diff --git a/acceptance/tests/modules/install/with_modulepath.rb b/acceptance/tests/modules/install/with_modulepath.rb new file mode 100644 index 000000000..1637ea5a8 --- /dev/null +++ b/acceptance/tests/modules/install/with_modulepath.rb @@ -0,0 +1,37 @@ +begin test_name "puppet module install (with modulepath)" + +step 'Setup' +require 'resolv'; ip = Resolv.getaddress('forge-dev.puppetlabs.lan') +apply_manifest_on master, "host { 'forge.puppetlabs.com': ip => '#{ip}' }" +apply_manifest_on master, "file { ['/etc/puppet/modules2']: ensure => directory, recurse => true, purge => true, force => true }" + +step "Install a module with relative modulepath" +on master, "cd /etc/puppet/modules2 && puppet module install pmtacceptance-nginx --modulepath=." do + assert_output <<-OUTPUT + Preparing to install into /etc/puppet/modules2 ... + Downloading from http://forge.puppetlabs.com ... + Installing -- do not interrupt ... + /etc/puppet/modules2 + └── pmtacceptance-nginx (\e[0;36mv0.0.1\e[0m) + OUTPUT +end +on master, '[ -d /etc/puppet/modules2/nginx ]' +apply_manifest_on master, "file { ['/etc/puppet/modules2']: ensure => directory, recurse => true, purge => true, force => true }" + +step "Install a module with absolute modulepath" +on master, puppet('module install pmtacceptance-nginx --modulepath=/etc/puppet/modules2') do + assert_output <<-OUTPUT + Preparing to install into /etc/puppet/modules2 ... + Downloading from http://forge.puppetlabs.com ... + Installing -- do not interrupt ... + /etc/puppet/modules2 + └── pmtacceptance-nginx (\e[0;36mv0.0.1\e[0m) + OUTPUT +end +on master, '[ -d /etc/puppet/modules2/nginx ]' +apply_manifest_on master, "file { ['/etc/puppet/modules2']: ensure => directory, recurse => true, purge => true, force => true }" + +ensure step "Teardown" +apply_manifest_on master, "host { 'forge.puppetlabs.com': ensure => absent }" +apply_manifest_on master, "file { ['/etc/puppet/modules2']: ensure => directory, recurse => true, purge => true, force => true }" +end diff --git a/acceptance/tests/modules/list/with_modulepath.rb b/acceptance/tests/modules/list/with_modulepath.rb new file mode 100644 index 000000000..02726db19 --- /dev/null +++ b/acceptance/tests/modules/list/with_modulepath.rb @@ -0,0 +1,76 @@ +begin test_name "puppet module list (with modulepath)" + +step "Setup" +apply_manifest_on master, <<-PP +file { + [ + '/etc/puppet/modules2', + '/etc/puppet/modules2/crakorn', + '/etc/puppet/modules2/appleseed', + '/etc/puppet/modules2/thelock', + ]: ensure => directory, + recurse => true, + purge => true, + force => true; + '/etc/puppet/modules2/crakorn/metadata.json': + content => '{ + "name": "jimmy/crakorn", + "version": "0.4.0", + "source": "", + "author": "jimmy", + "license": "MIT", + "dependencies": [] + }'; + '/etc/puppet/modules2/appleseed/metadata.json': + content => '{ + "name": "jimmy/appleseed", + "version": "1.1.0", + "source": "", + "author": "jimmy", + "license": "MIT", + "dependencies": [ + { "name": "jimmy/crakorn", "version_requirement": "0.4.0" } + ] + }'; + '/etc/puppet/modules2/thelock/metadata.json': + content => '{ + "name": "jimmy/thelock", + "version": "1.0.0", + "source": "", + "author": "jimmy", + "license": "MIT", + "dependencies": [ + { "name": "jimmy/appleseed", "version_requirement": "1.x" } + ] + }'; +} +PP +on master, '[ -d /etc/puppet/modules2/crakorn ]' +on master, '[ -d /etc/puppet/modules2/appleseed ]' +on master, '[ -d /etc/puppet/modules2/thelock ]' + +step "List the installed modules with relative modulepath" +on master, 'cd /etc/puppet/modules2 && puppet module list --modulepath=.' do + assert_equal '', stderr + assert_equal <<-STDOUT, stdout +/etc/puppet/modules2 +├── jimmy-appleseed (\e[0;36mv1.1.0\e[0m) +├── jimmy-crakorn (\e[0;36mv0.4.0\e[0m) +└── jimmy-thelock (\e[0;36mv1.0.0\e[0m) +STDOUT +end + +step "List the installed modules with absolute modulepath" +on master, puppet('module list --modulepath=/etc/puppet/modules2') do + assert_equal '', stderr + assert_equal <<-STDOUT, stdout +/etc/puppet/modules2 +├── jimmy-appleseed (\e[0;36mv1.1.0\e[0m) +├── jimmy-crakorn (\e[0;36mv0.4.0\e[0m) +└── jimmy-thelock (\e[0;36mv1.0.0\e[0m) +STDOUT +end + +ensure step "Teardown" +apply_manifest_on master, "file { ['/etc/puppet/modules2']: ensure => directory, recurse => true, purge => true, force => true }" +end diff --git a/acceptance/tests/modules/uninstall/with_modulepath.rb b/acceptance/tests/modules/uninstall/with_modulepath.rb new file mode 100644 index 000000000..e26ba1c62 --- /dev/null +++ b/acceptance/tests/modules/uninstall/with_modulepath.rb @@ -0,0 +1,54 @@ +begin test_name "puppet module uninstall (with modulepath)" + +step "Setup" +apply_manifest_on master, <<-PP +file { + [ + '/etc/puppet/modules2', + '/etc/puppet/modules2/crakorn', + '/etc/puppet/modules2/absolute', + ]: ensure => directory; + '/etc/puppet/modules2/crakorn/metadata.json': + content => '{ + "name": "jimmy/crakorn", + "version": "0.4.0", + "source": "", + "author": "jimmy", + "license": "MIT", + "dependencies": [] + }'; + '/etc/puppet/modules2/absolute/metadata.json': + content => '{ + "name": "jimmy/absolute", + "version": "0.4.0", + "source": "", + "author": "jimmy", + "license": "MIT", + "dependencies": [] + }'; +} +PP +on master, '[ -d /etc/puppet/modules2/crakorn ]' +on master, '[ -d /etc/puppet/modules2/absolute ]' + +step "Try to uninstall the module jimmy-crakorn using relative modulepath" +on master, 'cd /etc/puppet/modules2 && puppet module uninstall jimmy-crakorn --modulepath=.' do + assert_output <<-OUTPUT + Preparing to uninstall 'jimmy-crakorn' ... + Removed 'jimmy-crakorn' (\e[0;36mv0.4.0\e[0m) from /etc/puppet/modules2 + OUTPUT +end +on master, '[ ! -d /etc/puppet/modules2/crakorn ]' + +step "Try to uninstall the module jimmy-absolute using an absolute modulepath" +on master, 'cd /etc/puppet/modules2 && puppet module uninstall jimmy-absolute --modulepath=/etc/puppet/modules2' do + assert_output <<-OUTPUT + Preparing to uninstall 'jimmy-absolute' ... + Removed 'jimmy-absolute' (\e[0;36mv0.4.0\e[0m) from /etc/puppet/modules2 + OUTPUT +end +on master, '[ ! -d /etc/puppet/modules2/absolute ]' + +ensure step "Teardown" +apply_manifest_on master, "file { ['/etc/puppet/modules2']: ensure => directory, recurse => true, purge => true, force => true }" +end diff --git a/lib/puppet/face/module/install.rb b/lib/puppet/face/module/install.rb index 68eebec87..d7dc84536 100644 --- a/lib/puppet/face/module/install.rb +++ b/lib/puppet/face/module/install.rb @@ -1,173 +1,174 @@ # encoding: UTF-8 Puppet::Face.define(:module, '1.0.0') do action(:install) do summary "Install a module from the Puppet Forge or a release archive." description <<-EOT Installs a module from the Puppet Forge or from a release archive file. The specified module will be installed into the directory specified with the `--target-dir` option, which defaults to #{Puppet.settings[:modulepath].split(File::PATH_SEPARATOR).first}. EOT returns "Pathname object representing the path to the installed module." examples <<-EOT Install a module: $ puppet module install puppetlabs-vcsrepo Preparing to install into /etc/puppet/modules ... Downloading from http://forge.puppetlabs.com ... Installing -- do not interrupt ... /etc/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module to a specific environment: $ puppet module install puppetlabs-vcsrepo --environment development Preparing to install into /etc/puppet/environments/development/modules ... Downloading from http://forge.puppetlabs.com ... Installing -- do not interrupt ... /etc/puppet/environments/development/modules └── puppetlabs-vcsrepo (v0.0.4) Install a specific module version: $ puppet module install puppetlabs-vcsrepo -v 0.0.4 Preparing to install into /etc/puppet/modules ... Downloading from http://forge.puppetlabs.com ... Installing -- do not interrupt ... /etc/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module into a specific directory: $ puppet module install puppetlabs-vcsrepo --target-dir=/usr/share/puppet/modules Preparing to install into /usr/share/puppet/modules ... Downloading from http://forge.puppetlabs.com ... Installing -- do not interrupt ... /usr/share/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module into a specific directory and check for dependencies in other directories: $ puppet module install puppetlabs-vcsrepo --target-dir=/usr/share/puppet/modules --modulepath /etc/puppet/modules Preparing to install into /usr/share/puppet/modules ... Downloading from http://forge.puppetlabs.com ... Installing -- do not interrupt ... /usr/share/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module from a release archive: $ puppet module install puppetlabs-vcsrepo-0.0.4.tar.gz Preparing to install into /etc/puppet/modules ... Downloading from http://forge.puppetlabs.com ... Installing -- do not interrupt ... /etc/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) Install a module from a release archive and ignore dependencies: $ puppet module install puppetlabs-vcsrepo-0.0.4.tar.gz --ignore-dependencies Preparing to install into /etc/puppet/modules ... Installing -- do not interrupt ... /etc/puppet/modules └── puppetlabs-vcsrepo (v0.0.4) EOT arguments "" option "--force", "-f" do summary "Force overwrite of existing module, if any." description <<-EOT Force overwrite of existing module, if any. EOT end option "--target-dir DIR", "-i DIR" do summary "The directory into which modules are installed." description <<-EOT The directory into which modules are installed; defaults to the first directory in the modulepath. Specifying this option will change the installation directory, and will use the existing modulepath when checking for dependencies. If you wish to check a different set of directories for dependencies, you must also use the `--environment` or `--modulepath` options. EOT end option "--ignore-dependencies" do summary "Do not attempt to install dependencies" description <<-EOT Do not attempt to install dependencies. EOT end option "--modulepath MODULEPATH" do default_to { Puppet.settings[:modulepath] } summary "Which directories to look for modules in" description <<-EOT The list of directories to check for modules. When installing a new module, this setting determines where the module tool will look for its dependencies. If the `--target dir` option is not specified, the first directory in the modulepath will also be used as the install directory. When installing a module into an environment whose modulepath is specified in puppet.conf, you can use the `--environment` option instead, and its modulepath will be used automatically. This setting should be a list of directories separated by the path separator character. (The path separator is `:` on Unix-like platforms and `;` on Windows.) EOT end option "--version VER", "-v VER" do summary "Module version to install." description <<-EOT Module version to install; can be an exact version or a requirement string, eg '>= 1.0.3'. Defaults to latest version. EOT end option "--environment NAME" do default_to { "production" } summary "The target environment to install modules into." description <<-EOT The target environment to install modules into. Only applicable if multiple environments (with different modulepaths) have been configured in puppet.conf. EOT end when_invoked do |name, options| sep = File::PATH_SEPARATOR + if options[:target_dir] - options[:target_dir] = File.expand_path(options[:target_dir]) options[:modulepath] = "#{options[:target_dir]}#{sep}#{options[:modulepath]}" end Puppet.settings[:modulepath] = options[:modulepath] options[:target_dir] = Puppet.settings[:modulepath].split(sep).first + options[:target_dir] = File.expand_path(options[:target_dir]) Puppet.notice "Preparing to install into #{options[:target_dir]} ..." Puppet::ModuleTool::Applications::Installer.run(name, options) end when_rendering :console do |return_value, name, options| if return_value[:result] == :failure Puppet.err(return_value[:error][:multiline]) exit 1 else tree = Puppet::ModuleTool.build_tree(return_value[:installed_modules], return_value[:install_dir]) return_value[:install_dir] + "\n" + Puppet::ModuleTool.format_tree(tree) end end end end diff --git a/spec/unit/face/module/install_spec.rb b/spec/unit/face/module/install_spec.rb index ea774daf0..4af874de4 100644 --- a/spec/unit/face/module/install_spec.rb +++ b/spec/unit/face/module/install_spec.rb @@ -1,170 +1,181 @@ require 'spec_helper' require 'puppet/face' require 'puppet/module_tool' describe "puppet module install" do include PuppetSpec::Files subject { Puppet::Face[:module, :current] } let(:options) do {} end describe "option validation" do before do Puppet.settings[:modulepath] = fakemodpath end let(:expected_options) do { :target_dir => fakefirstpath, :modulepath => fakemodpath, :environment => 'production' } end let(:sep) { File::PATH_SEPARATOR } let(:fakefirstpath) { make_absolute("/my/fake/modpath") } let(:fakesecondpath) { make_absolute("/other/fake/path") } let(:fakemodpath) { "#{fakefirstpath}#{sep}#{fakesecondpath}" } let(:fakedirpath) { make_absolute("/my/fake/path") } context "without any options" do it "should require a name" do pattern = /wrong number of arguments/ expect { subject.install }.to raise_error ArgumentError, pattern end it "should not require any options" do Puppet::ModuleTool::Applications::Installer.expects(:run).with("puppetlabs-apache", expected_options).once subject.install("puppetlabs-apache") end end it "should accept the --force option" do options[:force] = true expected_options.merge!(options) Puppet::ModuleTool::Applications::Installer.expects(:run).with("puppetlabs-apache", expected_options).once subject.install("puppetlabs-apache", options) end it "should accept the --target-dir option" do options[:target_dir] = make_absolute("/foo/puppet/modules") expected_options.merge!(options) expected_options[:modulepath] = "#{options[:target_dir]}#{sep}#{fakemodpath}" Puppet::ModuleTool::Applications::Installer.expects(:run).with("puppetlabs-apache", expected_options).once subject.install("puppetlabs-apache", options) end it "should accept the --version option" do options[:version] = "0.0.1" expected_options.merge!(options) Puppet::ModuleTool::Applications::Installer.expects(:run).with("puppetlabs-apache", expected_options).once subject.install("puppetlabs-apache", options) end it "should accept the --ignore-dependencies option" do options[:ignore_dependencies] = true expected_options.merge!(options) Puppet::ModuleTool::Applications::Installer.expects(:run).with("puppetlabs-apache", expected_options).once subject.install("puppetlabs-apache", options) end describe "when modulepath option is passed" do let(:expected_options) { { :modulepath => fakemodpath, :environment => 'production' } } let(:options) { { :modulepath => fakemodpath } } describe "when target-dir option is not passed" do it "should set target-dir to be first path from modulepath" do expected_options[:target_dir] = fakefirstpath Puppet::ModuleTool::Applications::Installer. expects(:run). with("puppetlabs-apache", expected_options) Puppet::Face[:module, :current].install("puppetlabs-apache", options) Puppet.settings[:modulepath].should == fakemodpath end + + it "should expand the target directory derived from the modulepath" do + options[:modulepath] = "modules" + expanded_path = File.expand_path("modules") + expected_options.merge!(options) + expected_options[:target_dir] = expanded_path + expected_options[:modulepath] = "modules" + + Puppet::ModuleTool::Applications::Installer.expects(:run).with("puppetlabs-apache", expected_options).once + subject.install("puppetlabs-apache", options) + end end describe "when target-dir option is passed" do - it "should expand the target directory" do + it "should expand the target directory when target_dir is set" do options[:target_dir] = "modules" expanded_path = File.expand_path("modules") expected_options.merge!(options) expected_options[:target_dir] = expanded_path - expected_options[:modulepath] = "#{expanded_path}#{sep}#{fakemodpath}" + expected_options[:modulepath] = "modules#{sep}#{fakemodpath}" Puppet::ModuleTool::Applications::Installer.expects(:run).with("puppetlabs-apache", expected_options).once subject.install("puppetlabs-apache", options) end it "should set target-dir to be first path of modulepath" do options[:target_dir] = fakedirpath expected_options[:target_dir] = fakedirpath expected_options[:modulepath] = "#{fakedirpath}#{sep}#{fakemodpath}" Puppet::ModuleTool::Applications::Installer. expects(:run). with("puppetlabs-apache", expected_options) Puppet::Face[:module, :current].install("puppetlabs-apache", options) Puppet.settings[:modulepath].should == "#{fakedirpath}#{sep}#{fakemodpath}" end end end describe "when modulepath option is not passed" do before do Puppet.settings[:modulepath] = fakemodpath end describe "when target-dir option is not passed" do it "should set target-dir to be first path of default mod path" do expected_options[:target_dir] = fakefirstpath expected_options[:modulepath] = fakemodpath Puppet::ModuleTool::Applications::Installer. expects(:run). with("puppetlabs-apache", expected_options) Puppet::Face[:module, :current].install("puppetlabs-apache", options) end end describe "when target-dir option is passed" do it "should prepend target-dir to modulepath" do options[:target_dir] = fakedirpath expected_options[:target_dir] = fakedirpath expected_options[:modulepath] = "#{options[:target_dir]}#{sep}#{fakemodpath}" Puppet::ModuleTool::Applications::Installer. expects(:run). with("puppetlabs-apache", expected_options) Puppet::Face[:module, :current].install("puppetlabs-apache", options) Puppet.settings[:modulepath].should == expected_options[:modulepath] end end end end describe "inline documentation" do subject { Puppet::Face[:module, :current].get_action :install } its(:summary) { should =~ /install.*module/im } its(:description) { should =~ /install.*module/im } its(:returns) { should =~ /pathname/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