diff --git a/lib/puppet/face/module/uninstall.rb b/lib/puppet/face/module/uninstall.rb new file mode 100644 index 000000000..19c983ea6 --- /dev/null +++ b/lib/puppet/face/module/uninstall.rb @@ -0,0 +1,50 @@ +Puppet::Face.define(:module, '1.0.0') do + action(:uninstall) do + summary "Uninstall a puppet module." + description <<-EOT + Uninstall a puppet module from the modulepath or a specific + target directory which defaults to + #{Puppet.settings[:modulepath].split(File::PATH_SEPARATOR).join(', ')}. + EOT + + returns "Array of strings representing paths of uninstalled files." + + examples <<-EOT + Uninstall a module from all directories in the modulepath: + + $ puppet module uninstall ssh + Removed /etc/puppet/modules/ssh + + Uninstall a module from a specific directory: + + $ puppet module uninstall --target-directory /usr/share/puppet/modules ssh + Removed /usr/share/puppet/modules/ssh + EOT + + arguments "" + + option "--target-directory=", "-t=" do + default_to { Puppet.settings[:modulepath].split(File::PATH_SEPARATOR) } + summary "The target directory to search from modules." + description <<-EOT + The target directory to search for modules. + EOT + end + + when_invoked do |name, options| + + if options[:target_directory].is_a?(Array) + options[:target_directories] = options[:target_directory] + else + options[:target_directories] = [ options[:target_directory] ] + end + options.delete(:target_directory) + + Puppet::Module::Tool::Applications::Uninstaller.run(name, options) + end + + when_rendering :console do |removed_modules| + removed_modules.map { |path| "Removed #{path}" }.join('\n') + end + end +end diff --git a/lib/puppet/module_tool/applications.rb b/lib/puppet/module_tool/applications.rb index e7d54dc00..24bcbe279 100644 --- a/lib/puppet/module_tool/applications.rb +++ b/lib/puppet/module_tool/applications.rb @@ -1,12 +1,13 @@ module Puppet::Module::Tool module Applications require 'puppet/module_tool/applications/application' require 'puppet/module_tool/applications/builder' require 'puppet/module_tool/applications/checksummer' require 'puppet/module_tool/applications/cleaner' require 'puppet/module_tool/applications/generator' require 'puppet/module_tool/applications/installer' require 'puppet/module_tool/applications/searcher' require 'puppet/module_tool/applications/unpacker' + require 'puppet/module_tool/applications/uninstaller' end end diff --git a/lib/puppet/module_tool/applications/uninstaller.rb b/lib/puppet/module_tool/applications/uninstaller.rb new file mode 100644 index 000000000..9cd4d8bd3 --- /dev/null +++ b/lib/puppet/module_tool/applications/uninstaller.rb @@ -0,0 +1,33 @@ +module Puppet::Module::Tool + module Applications + class Uninstaller < Application + + def initialize(name, options = {}) + @name = name + @target_directories = options[:target_directories] + @removed_dirs = [] + end + + def run + uninstall + Puppet.notice "#{@name} is not installed" if @removed_dirs.empty? + @removed_dirs + end + + private + + def uninstall + # TODO: #11803 Check for broken dependencies before uninstalling modules. + # + # Search each path in the target directories for the specified module + # and delete the directory. + @target_directories.each do |target| + if File.directory? target + module_path = File.join(target, @name) + @removed_dirs << FileUtils.rm_rf(module_path).first if File.directory?(module_path) + end + end + end + end + end +end diff --git a/spec/unit/face/module/uninstall_spec.rb b/spec/unit/face/module/uninstall_spec.rb new file mode 100644 index 000000000..6f3be6e1e --- /dev/null +++ b/spec/unit/face/module/uninstall_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' +require 'puppet/face' +require 'puppet/module_tool' + +describe "puppet module uninstall" do + subject { Puppet::Face[:module, :current] } + + let(:options) do + {} + end + + describe "option validation" do + context "without any options" do + it "should require a name" do + pattern = /wrong number of arguments/ + expect { subject.uninstall }.to raise_error ArgumentError, pattern + end + + it "should not require any options" do + Puppet::Module::Tool::Applications::UnInstaller.expects(:run).once + subject.uninstall("puppetlabs-apache") + end + end + + it "should accept the --target-directory option" do + options[:target_directory] = "/foo/puppet/modules" + expected_options = { :target_directories => ["/foo/puppet/modules"] } + Puppet::Module::Tool::Applications::UnInstaller.expects(:run).with("puppetlabs-apache", expected_options).once + subject.uninstall("puppetlabs-apache", options) + end + end + + describe "inline documentation" do + subject { Puppet::Face[:module, :current].get_action :uninstall } + + its(:summary) { should =~ /uninstall.*module/im } + its(:description) { should =~ /uninstall.*module/im } + its(:returns) { should =~ /array of strings/i } + its(:examples) { should_not be_empty } + + %w{ license copyright summary description returns examples }.each do |doc| + context "of the" do + its(doc.to_sym) { should_not =~ /(FIXME|REVISIT|TODO)/ } + end + end + end +end diff --git a/spec/unit/module_tool/uninstaller_spec.rb b/spec/unit/module_tool/uninstaller_spec.rb new file mode 100644 index 000000000..abf2db0f8 --- /dev/null +++ b/spec/unit/module_tool/uninstaller_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' +require 'puppet/module_tool' +require 'tmpdir' + +describe Puppet::Module::Tool::Applications::Uninstaller do + include PuppetSpec::Files + + describe "instances" do + let(:tmp_module_path1) { tmpdir("uninstaller_module_path1") } + let(:tmp_module_path2) { tmpdir("uninstaller_module_path2") } + let(:options) do + { :target_directories => [ tmp_module_path1, tmp_module_path2 ] } + end + + it "should return an empty list if the module is not installed" do + described_class.new('foo', options).run.should == [] + end + + it "should uninstall an installed module" do + foo_module_path = File.join(tmp_module_path1, 'foo') + Dir.mkdir(foo_module_path) + described_class.new('foo', options).run.should == [ foo_module_path ] + end + + it "should only uninstall the requested module" do + foo_module_path = File.join(tmp_module_path1, 'foo') + bar_module_path = File.join(tmp_module_path1, 'bar') + Dir.mkdir(foo_module_path) + Dir.mkdir(bar_module_path) + described_class.new('foo', options).run.should == [ foo_module_path ] + end + + it "should uninstall the module from all target directories" do + foo1_module_path = File.join(tmp_module_path1, 'foo') + foo2_module_path = File.join(tmp_module_path2, 'foo') + Dir.mkdir(foo1_module_path) + Dir.mkdir(foo2_module_path) + described_class.new('foo', options).run.should == [ foo1_module_path, foo2_module_path ] + end + + #11803 + it "should check for broken dependencies" + end +end