diff --git a/lib/puppet/face/module/install.rb b/lib/puppet/face/module/install.rb index 54489b1ef..ee9b64d20 100644 --- a/lib/puppet/face/module/install.rb +++ b/lib/puppet/face/module/install.rb @@ -1,166 +1,136 @@ # encoding: UTF-8 Puppet::Face.define(:module, '1.0.0') do action(:install) do summary "Install a module from a repository or release archive." description <<-EOT Installs a module from the Puppet Forge, from a release archive file on-disk, or from a private Forge-like repository. 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| Puppet::Module::Tool.set_option_defaults options Puppet.notice "Preparing to install into #{options[:target_dir]} ..." Puppet::Module::Tool::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::Module::Tool.build_tree(return_value[:installed_modules], return_value[:install_dir]) return_value[:install_dir] + "\n" + Puppet::Module::Tool.format_tree(tree) end end end end diff --git a/lib/puppet/face/module/list.rb b/lib/puppet/face/module/list.rb index 5ff6fa188..4181741d2 100644 --- a/lib/puppet/face/module/list.rb +++ b/lib/puppet/face/module/list.rb @@ -1,285 +1,268 @@ # encoding: UTF-8 Puppet::Face.define(:module, '1.0.0') do action(:list) do summary "List installed modules" description <<-HEREDOC Lists the installed puppet modules. By default, this action scans the modulepath from puppet.conf's `[main]` block; use the --modulepath option to change which directories are scanned. The output of this action includes information from the module's metadata, including version numbers and unmet module dependencies. HEREDOC returns "hash of paths to module objects" -# option "--environment NAME" do -# default_to { "production" } -# summary "Which environments' modules to list" -# description <<-EOT -# Which environments' modules to list. -# EOT -# end - -# option "--modulepath MODULEPATH" do -# summary "Which directories to look for modules in" -# description <<-EOT -# Which directories to look for modules in; use the system path separator -# character (`:` on Unix-like systems and `;` on Windows) to specify -# multiple directories. -# EOT -# end - option "--tree" do summary "Whether to show dependencies as a tree view" end examples <<-EOT List installed modules: $ puppet module list /etc/puppet/modules ├── bodepd-create_resources (v0.0.1) ├── puppetlabs-bacula (v0.0.2) ├── puppetlabs-mysql (v0.0.1) ├── puppetlabs-sqlite (v0.0.1) └── puppetlabs-stdlib (v2.2.1) /usr/share/puppet/modules (no modules installed) List installed modules in a tree view: $ puppet module list --tree /etc/puppet/modules └─┬ puppetlabs-bacula (v0.0.2) ├── puppetlabs-stdlib (v2.2.1) ├─┬ puppetlabs-mysql (v0.0.1) │ └── bodepd-create_resources (v0.0.1) └── puppetlabs-sqlite (v0.0.1) /usr/share/puppet/modules (no modules installed) List installed modules from a specified environment: $ puppet module list --environment production /etc/puppet/modules ├── bodepd-create_resources (v0.0.1) ├── puppetlabs-bacula (v0.0.2) ├── puppetlabs-mysql (v0.0.1) ├── puppetlabs-sqlite (v0.0.1) └── puppetlabs-stdlib (v2.2.1) /usr/share/puppet/modules (no modules installed) List installed modules from a specified modulepath: $ puppet module list --modulepath /usr/share/puppet/modules /usr/share/puppet/modules (no modules installed) EOT when_invoked do |options| Puppet[:modulepath] = options[:modulepath] if options[:modulepath] environment = Puppet::Node::Environment.new(options[:environment]) environment.modules_by_path end when_rendering :console do |modules_by_path, options| output = '' Puppet[:modulepath] = options[:modulepath] if options[:modulepath] environment = Puppet::Node::Environment.new(options[:environment]) error_types = { :non_semantic_version => { :title => "Non semantic version dependency" }, :missing => { :title => "Missing dependency" }, :version_mismatch => { :title => "Module '%s' (v%s) fails to meet some dependencies:" } } @unmet_deps = {} error_types.each_key do |type| @unmet_deps[type] = Hash.new do |hash, key| hash[key] = { :errors => [], :parent => nil } end end # Prepare the unmet dependencies for display on the console. environment.modules.sort_by {|mod| mod.name}.each do |mod| unmet_grouped = Hash.new { |h,k| h[k] = [] } unmet_grouped = mod.unmet_dependencies.inject(unmet_grouped) do |acc, dep| acc[dep[:reason]] << dep acc end unmet_grouped.each do |type, deps| unless deps.empty? unmet_grouped[type].sort_by { |dep| dep[:name] }.each do |dep| dep_name = dep[:name].gsub('/', '-') installed_version = dep[:mod_details][:installed_version] version_constraint = dep[:version_constraint] parent_name = dep[:parent][:name].gsub('/', '-') parent_version = dep[:parent][:version] msg = "'#{parent_name}' (#{parent_version})" msg << " requires '#{dep_name}' (#{version_constraint})" @unmet_deps[type][dep[:name]][:errors] << msg @unmet_deps[type][dep[:name]][:parent] = { :name => dep[:parent][:name], :version => parent_version } @unmet_deps[type][dep[:name]][:version] = installed_version end end end end # Display unmet dependencies by category. error_display_order = [:non_semantic_version, :version_mismatch, :missing] error_display_order.each do |type| unless @unmet_deps[type].empty? @unmet_deps[type].keys.sort_by {|dep| dep }.each do |dep| name = dep.gsub('/', '-') title = error_types[type][:title] errors = @unmet_deps[type][dep][:errors] version = @unmet_deps[type][dep][:version] msg = case type when :version_mismatch title % [name, version] + "\n" when :non_semantic_version title + " '#{name}' (v#{version}):\n" else title + " '#{name}':\n" end errors.each { |error_string| msg << " #{error_string}\n" } Puppet.warning msg.chomp end end end environment.modulepath.each do |path| modules = modules_by_path[path] no_mods = modules.empty? ? ' (no modules installed)' : '' output << "#{path}#{no_mods}\n" if options[:tree] # The modules with fewest things depending on them will be the # parent of the tree. Can't assume to start with 0 dependencies # since dependencies may be cyclical. modules_by_num_requires = modules.sort_by {|m| m.required_by.size} @seen = {} tree = list_build_tree(modules_by_num_requires, [], nil, :label_unmet => true, :path => path, :label_invalid => false) else tree = [] modules.sort_by { |mod| mod.forge_name or mod.name }.each do |mod| tree << list_build_node(mod, path, :label_unmet => false, :path => path, :label_invalid => true) end end output << Puppet::Module::Tool.format_tree(tree) end output end end # Prepare a list of module objects and their dependencies for print in a # tree view. # # Returns an Array of Hashes # # Example: # # [ # { # :text => "puppetlabs-bacula (v0.0.2)", # :dependencies=> [ # { :text => "puppetlabs-stdlib (v2.2.1)", :dependencies => [] }, # { # :text => "puppetlabs-mysql (v1.0.0)" # :dependencies => [ # { # :text => "bodepd-create_resources (v0.0.1)", # :dependencies => [] # } # ] # }, # { :text => "puppetlabs-sqlite (v0.0.1)", :dependencies => [] }, # ] # } # ] # # When the above data structure is passed to Puppet::Module::Tool.build_tree # you end up with something like this: # # /etc/puppet/modules # └─┬ puppetlabs-bacula (v0.0.2) # ├── puppetlabs-stdlib (v2.2.1) # ├─┬ puppetlabs-mysql (v1.0.0) # │ └── bodepd-create_resources (v0.0.1) # └── puppetlabs-sqlite (v0.0.1) # def list_build_tree(list, ancestors=[], parent=nil, params={}) list.map do |mod| next if @seen[(mod.forge_name or mod.name)] node = list_build_node(mod, parent, params) @seen[(mod.forge_name or mod.name)] = true unless ancestors.include?(mod) node[:dependencies] ||= [] missing_deps = mod.unmet_dependencies.select do |dep| dep[:reason] == :missing end missing_deps.map do |mis_mod| str = "#{colorize(:bg_red, 'UNMET DEPENDENCY')} #{mis_mod[:name].gsub('/', '-')} " str << "(#{colorize(:cyan, mis_mod[:version_constraint])})" node[:dependencies] << { :text => str } end node[:dependencies] += list_build_tree(mod.dependencies_as_modules, ancestors + [mod], mod, params) end node end.compact end # Prepare a module object for print in a tree view. Each node in the tree # must be a Hash in the following format: # # { :text => "puppetlabs-mysql (v1.0.0)" } # # The value of a module's :text is affected by three (3) factors: the format # of the tree, it's dependency status, and the location in the modulepath # relative to it's parent. # # Returns a Hash # def list_build_node(mod, parent, params) str = '' str << (mod.forge_name ? mod.forge_name.gsub('/', '-') : mod.name) str << ' (' + colorize(:cyan, mod.version ? "v#{mod.version}" : '???') + ')' unless File.dirname(mod.path) == params[:path] str << " [#{File.dirname(mod.path)}]" end if @unmet_deps[:version_mismatch].include?(mod.forge_name) if params[:label_invalid] str << ' ' + colorize(:red, 'invalid') elsif parent.respond_to?(:forge_name) unmet_parent = @unmet_deps[:version_mismatch][mod.forge_name][:parent] if (unmet_parent[:name] == parent.forge_name && unmet_parent[:version] == "v#{parent.version}") str << ' ' + colorize(:red, 'invalid') end end end { :text => str } end end diff --git a/lib/puppet/face/module/uninstall.rb b/lib/puppet/face/module/uninstall.rb index 886f3a2b7..9d42ff5f9 100644 --- a/lib/puppet/face/module/uninstall.rb +++ b/lib/puppet/face/module/uninstall.rb @@ -1,86 +1,71 @@ Puppet::Face.define(:module, '1.0.0') do action(:uninstall) do summary "Uninstall a puppet module." description <<-EOT Uninstalls a puppet module from the modulepath (or a specific target directory). EOT returns "Hash of module objects representing uninstalled modules and related errors." examples <<-EOT Uninstall a module: $ puppet module uninstall puppetlabs-ssh Removed /etc/puppet/modules/ssh (v1.0.0) Uninstall a module from a specific directory: $ puppet module uninstall puppetlabs-ssh --modulepath /usr/share/puppet/modules Removed /usr/share/puppet/modules/ssh (v1.0.0) Uninstall a module from a specific environment: $ puppet module uninstall puppetlabs-ssh --environment development Removed /etc/puppet/environments/development/modules/ssh (v1.0.0) Uninstall a specific version of a module: $ puppet module uninstall puppetlabs-ssh --version 2.0.0 Removed /etc/puppet/modules/ssh (v2.0.0) EOT arguments "" option "--force", "-f" do summary "Force uninstall of an installed module." description <<-EOT Force the uninstall of an installed module even if there are local changes or the possibility of causing broken dependencies. EOT end -# option "--environment NAME" do -# default_to { "production" } -# summary "The target environment to uninstall modules from." -# description <<-EOT -# The target environment to uninstall modules from. -# EOT -# end - option "--version=" do summary "The version of the module to uninstall" description <<-EOT The version of the module to uninstall. When using this option, a module matching the specified version must be installed or else an error is raised. EOT end -# option "--modulepath=" do -# summary "The target directory to search for modules." -# description <<-EOT -# The target directory to search for modules. -# EOT -# end - when_invoked do |name, options| name = name.gsub('/', '-') Puppet::Module::Tool.set_option_defaults options Puppet.notice "Preparing to uninstall '#{name}'" << (options[:version] ? " (#{colorize(:cyan, options[:version].sub(/^(?=\d)/, 'v'))})" : '') << " ..." Puppet::Module::Tool::Applications::Uninstaller.run(name, options) end when_rendering :console do |return_value| if return_value[:result] == :failure Puppet.err(return_value[:error][:multiline]) exit 1 else mod = return_value[:affected_modules].first "Removed '#{return_value[:module_name]}'" << (mod.version ? " (#{colorize(:cyan, mod.version.to_s.sub(/^(?=\d)/, 'v'))})" : '') << " from #{mod.modulepath}" end end end end diff --git a/lib/puppet/face/module/upgrade.rb b/lib/puppet/face/module/upgrade.rb index d77e39494..d1cea8121 100644 --- a/lib/puppet/face/module/upgrade.rb +++ b/lib/puppet/face/module/upgrade.rb @@ -1,85 +1,77 @@ # encoding: UTF-8 Puppet::Face.define(:module, '1.0.0') do action(:upgrade) do summary "Upgrade a puppet module." description <<-EOT Upgrades a puppet module. EOT returns "Hash" examples <<-EOT upgrade an installed module to the latest version $ puppet module upgrade puppetlabs-apache /etc/puppet/modules └── puppetlabs-apache (v1.0.0 -> v2.4.0) upgrade an installed module to a specific version $ puppet module upgrade puppetlabs-apache --version 2.1.0 /etc/puppet/modules └── puppetlabs-apache (v1.0.0 -> v2.1.0) upgrade an installed module for a specific environment $ puppet module upgrade puppetlabs-apache --environment test /usr/share/puppet/environments/test/modules └── puppetlabs-apache (v1.0.0 -> v2.4.0) EOT arguments "" option "--force", "-f" do summary "Force upgrade of an installed module." description <<-EOT Force the upgrade of an installed module even if there are local changes or the possibility of causing broken dependencies. EOT end option "--ignore-dependencies" do summary "Do not attempt to install dependencies." description <<-EOT Do not attempt to install dependencies EOT end -# option "--environment NAME" do -# default_to { "production" } -# summary "The target environment to search for modules." -# description <<-EOT -# The target environment to search for modules. -# EOT -# end - option "--version=" do summary "The version of the module to upgrade to." description <<-EOT The version of the module to upgrade to. EOT end when_invoked do |name, options| name = name.gsub('/', '-') Puppet.notice "Preparing to upgrade '#{name}' ..." Puppet::Module::Tool.set_option_defaults options Puppet::Module::Tool::Applications::Upgrader.new(name, options).run end when_rendering :console do |return_value| if return_value[:result] == :failure Puppet.err(return_value[:error][:multiline]) exit 1 elsif return_value[:result] == :noop Puppet.err(return_value[:error][:multiline]) exit 0 else tree = Puppet::Module::Tool.build_tree(return_value[:affected_modules], return_value[:base_dir]) return_value[:base_dir] + "\n" + Puppet::Module::Tool.format_tree(tree) end end end end