diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index a2a249d1c..07daa80e6 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -1,342 +1,340 @@ require 'puppet/util/logging' require 'semver' require 'json' # Support for modules class Puppet::Module class Error < Puppet::Error; end class MissingModule < Error; end class IncompatibleModule < Error; end class UnsupportedPlatform < Error; end class IncompatiblePlatform < Error; end class MissingMetadata < Error; end class InvalidName < Error; end class InvalidFilePattern < Error; end include Puppet::Util::Logging FILETYPES = { "manifests" => "manifests", "files" => "files", "templates" => "templates", "plugins" => "lib", "pluginfacts" => "facts.d", } # Find and return the +module+ that +path+ belongs to. If +path+ is # absolute, or if there is no module whose name is the first component # of +path+, return +nil+ def self.find(modname, environment = nil) return nil unless modname # Unless a specific environment is given, use the current environment env = environment ? Puppet.lookup(:environments).get!(environment) : Puppet.lookup(:current_environment) env.module(modname) end attr_reader :name, :environment, :path, :metadata attr_writer :environment attr_accessor :dependencies, :forge_name attr_accessor :source, :author, :version, :license, :puppetversion, :summary, :description, :project_page def initialize(name, path, environment) @name = name @path = path @environment = environment assert_validity load_metadata if has_metadata? validate_puppet_version @absolute_path_to_manifests = Puppet::FileSystem::PathPattern.absolute(manifests) end def has_metadata? return false unless metadata_file return false unless Puppet::FileSystem.exist?(metadata_file) begin metadata = JSON.parse(File.read(metadata_file)) rescue JSON::JSONError => e Puppet.debug("#{name} has an invalid and unparsable metadata.json file. The parse error: #{e.message}") return false end return metadata.is_a?(Hash) && !metadata.keys.empty? end FILETYPES.each do |type, location| # A boolean method to let external callers determine if # we have files of a given type. define_method(type +'?') do type_subpath = subpath(location) unless Puppet::FileSystem.exist?(type_subpath) Puppet.debug("No #{type} found in subpath '#{type_subpath}' " + "(file / directory does not exist)") return false end return true end # A method for returning a given file of a given type. # e.g., file = mod.manifest("my/manifest.pp") # # If the file name is nil, then the base directory for the # file type is passed; this is used for fileserving. define_method(type.sub(/s$/, '')) do |file| # If 'file' is nil then they're asking for the base path. # This is used for things like fileserving. if file full_path = File.join(subpath(location), file) else full_path = subpath(location) end return nil unless Puppet::FileSystem.exist?(full_path) return full_path end # Return the base directory for the given type define_method(type) do subpath(location) end end def license_file return @license_file if defined?(@license_file) return @license_file = nil unless path @license_file = File.join(path, "License") end def load_metadata @metadata = data = JSON.parse(File.read(metadata_file)) @forge_name = data['name'].gsub('-', '/') if data['name'] [:source, :author, :version, :license, :puppetversion, :dependencies].each do |attr| unless value = data[attr.to_s] unless attr == :puppetversion raise MissingMetadata, "No #{attr} module metadata provided for #{self.name}" end end # NOTICE: The fallback to `versionRequirement` is something we'd like to # not have to support, but we have a reasonable number of releases that # don't use `version_requirement`. When we can deprecate this, we should. if attr == :dependencies - value.tap do |dependencies| - dependencies.each do |dep| - dep['version_requirement'] ||= dep['versionRequirement'] || '>= 0.0.0' - end + value.each do |dep| + dep['version_requirement'] ||= dep['versionRequirement'] || '>= 0.0.0' end end send(attr.to_s + "=", value) end end # Return the list of manifests matching the given glob pattern, # defaulting to 'init.pp' for empty modules. def match_manifests(rest) if rest wanted_manifests = wanted_manifests_from(rest) searched_manifests = wanted_manifests.glob.reject { |f| FileTest.directory?(f) } else searched_manifests = [] end # (#4220) Always ensure init.pp in case class is defined there. init_manifest = manifest("init.pp") if !init_manifest.nil? && !searched_manifests.include?(init_manifest) searched_manifests.unshift(init_manifest) end searched_manifests end def all_manifests return [] unless Puppet::FileSystem.exist?(manifests) Dir.glob(File.join(manifests, '**', '*.pp')) end def metadata_file return @metadata_file if defined?(@metadata_file) return @metadata_file = nil unless path @metadata_file = File.join(path, "metadata.json") end def modulepath File.dirname(path) if path end # Find all plugin directories. This is used by the Plugins fileserving mount. def plugin_directory subpath("lib") end def plugin_fact_directory subpath("facts.d") end def has_external_facts? File.directory?(plugin_fact_directory) end def supports(name, version = nil) @supports ||= [] @supports << [name, version] end def to_s result = "Module #{name}" result += "(#{path})" if path result end def dependencies_as_modules dependent_modules = [] dependencies and dependencies.each do |dep| author, dep_name = dep["name"].split('/') found_module = environment.module(dep_name) dependent_modules << found_module if found_module end dependent_modules end def required_by environment.module_requirements[self.forge_name] || {} end def has_local_changes? Puppet.deprecation_warning("This method is being removed.") require 'puppet/module_tool/applications' changes = Puppet::ModuleTool::Applications::Checksummer.run(path) !changes.empty? end def local_changes Puppet.deprecation_warning("This method is being removed.") require 'puppet/module_tool/applications' Puppet::ModuleTool::Applications::Checksummer.run(path) end # Identify and mark unmet dependencies. A dependency will be marked unmet # for the following reasons: # # * not installed and is thus considered missing # * installed and does not meet the version requirements for this module # * installed and doesn't use semantic versioning # # Returns a list of hashes representing the details of an unmet dependency. # # Example: # # [ # { # :reason => :missing, # :name => 'puppetlabs-mysql', # :version_constraint => 'v0.0.1', # :mod_details => { # :installed_version => '0.0.1' # } # :parent => { # :name => 'puppetlabs-bacula', # :version => 'v1.0.0' # } # } # ] # def unmet_dependencies unmet_dependencies = [] return unmet_dependencies unless dependencies dependencies.each do |dependency| forge_name = dependency['name'] version_string = dependency['version_requirement'] || '>= 0.0.0' dep_mod = begin environment.module_by_forge_name(forge_name) rescue nil end error_details = { :name => forge_name, :version_constraint => version_string.gsub(/^(?=\d)/, "v"), :parent => { :name => self.forge_name, :version => self.version.gsub(/^(?=\d)/, "v") }, :mod_details => { :installed_version => dep_mod.nil? ? nil : dep_mod.version } } unless dep_mod error_details[:reason] = :missing unmet_dependencies << error_details next end if version_string begin required_version_semver_range = SemVer[version_string] actual_version_semver = SemVer.new(dep_mod.version) rescue ArgumentError error_details[:reason] = :non_semantic_version unmet_dependencies << error_details next end unless required_version_semver_range.include? actual_version_semver error_details[:reason] = :version_mismatch unmet_dependencies << error_details next end end end unmet_dependencies end def validate_puppet_version return unless puppetversion and puppetversion != Puppet.version raise IncompatibleModule, "Module #{self.name} is only compatible with Puppet version #{puppetversion}, not #{Puppet.version}" end def ==(other) self.name == other.name && self.version == other.version && self.path == other.path && self.environment == other.environment end private def wanted_manifests_from(pattern) begin extended = File.extname(pattern).empty? ? "#{pattern}.pp" : pattern relative_pattern = Puppet::FileSystem::PathPattern.relative(extended) rescue Puppet::FileSystem::PathPattern::InvalidPattern => error raise Puppet::Module::InvalidFilePattern.new( "The pattern \"#{pattern}\" to find manifests in the module \"#{name}\" " + "is invalid and potentially unsafe.", error) end relative_pattern.prefix_with(@absolute_path_to_manifests) end def subpath(type) File.join(path, type) end def assert_validity raise InvalidName, "Invalid module name #{name}; module names must be alphanumeric (plus '-'), not '#{name}'" unless name =~ /^[-\w]+$/ end end diff --git a/lib/puppet/module_tool/applications/installer.rb b/lib/puppet/module_tool/applications/installer.rb index 7ce533073..e47cb80bd 100644 --- a/lib/puppet/module_tool/applications/installer.rb +++ b/lib/puppet/module_tool/applications/installer.rb @@ -1,353 +1,351 @@ require 'open-uri' require 'pathname' require 'fileutils' require 'tmpdir' require 'puppet/forge' require 'puppet/module_tool' require 'puppet/module_tool/shared_behaviors' require 'puppet/module_tool/install_directory' require 'puppet/module_tool/local_tarball' require 'puppet/module_tool/installed_modules' module Puppet::ModuleTool module Applications class Installer < Application include Puppet::ModuleTool::Errors include Puppet::Forge::Errors def initialize(name, install_dir, options = {}) super(options) @action = :install @environment = options[:environment_instance] @ignore_dependencies = forced? || options[:ignore_dependencies] @name = name @install_dir = install_dir Puppet::Forge::Cache.clean @local_tarball = Puppet::FileSystem.exist?(name) if @local_tarball release = local_tarball_source.release @name = release.name options[:version] = release.version.to_s Semantic::Dependency.add_source(local_tarball_source) # If we're operating on a local tarball and ignoring dependencies, we # don't need to search any additional sources. This will cut down on # unnecessary network traffic. unless @ignore_dependencies Semantic::Dependency.add_source(installed_modules_source) Semantic::Dependency.add_source(module_repository) end else Semantic::Dependency.add_source(installed_modules_source) unless forced? Semantic::Dependency.add_source(module_repository) end end def run name = @name.tr('/', '-') version = options[:version] || '>= 0.0.0' results = { :action => :install, :module_name => name, :module_version => version } begin if installed_module = installed_modules[name] unless forced? if Semantic::VersionRange.parse(version).include? installed_module.version results[:result] = :noop results[:version] = installed_module.version return results else changes = Checksummer.run(installed_modules[name].mod.path) rescue [] raise AlreadyInstalledError, :module_name => name, :installed_version => installed_modules[name].version, :requested_version => options[:version] || :latest, :local_changes => changes end end end @install_dir.prepare(name, options[:version] || 'latest') results[:install_dir] = @install_dir.target unless @local_tarball && @ignore_dependencies Puppet.notice "Downloading from #{module_repository.host} ..." end if @ignore_dependencies graph = build_single_module_graph(name, version) else graph = build_dependency_graph(name, version) end unless forced? add_module_name_constraints_to_graph(graph) end installed_modules.each do |mod, release| mod = mod.tr('/', '-') next if mod == name version = release.version unless forced? # Since upgrading already installed modules can be troublesome, # we'll place constraints on the graph for each installed module, # locking it to upgrades within the same major version. - ">=#{version} #{version.major}.x".tap do |range| - graph.add_constraint('installed', mod, range) do |node| - Semantic::VersionRange.parse(range).include? node.version - end + installed_range = ">=#{version} #{version.major}.x" + graph.add_constraint('installed', mod, installed_range) do |node| + Semantic::VersionRange.parse(installed_range).include? node.version end release.mod.dependencies.each do |dep| dep_name = dep['name'].tr('/', '-') - dep['version_requirement'].tap do |range| - graph.add_constraint("#{mod} constraint", dep_name, range) do |node| - Semantic::VersionRange.parse(range).include? node.version - end + range = dep['version_requirement'] + graph.add_constraint("#{mod} constraint", dep_name, range) do |node| + Semantic::VersionRange.parse(range).include? node.version end end end end # Ensure that there is at least one candidate release available # for the target package. if graph.dependencies[name].empty? raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host, :requested_version => options[:version] || :latest) end begin Puppet.info "Resolving dependencies ..." releases = Semantic::Dependency.resolve(graph) rescue Semantic::Dependency::UnsatisfiableGraph raise NoVersionsSatisfyError, results.merge(:requested_name => name) end unless forced? # Check for module name conflicts. releases.each do |rel| if installed_module = installed_modules_source.by_name[rel.name.split('-').last] next if installed_module.has_metadata? && installed_module.forge_name.tr('/', '-') == rel.name if rel.name != name dependency = { :name => rel.name, :version => rel.version } end raise InstallConflictError, :requested_module => name, :requested_version => options[:version] || 'latest', :dependency => dependency, :directory => installed_module.path, :metadata => installed_module.metadata end end end Puppet.info "Preparing to install ..." releases.each { |release| release.prepare } Puppet.notice 'Installing -- do not interrupt ...' releases.each do |release| installed = installed_modules[release.name] if forced? || installed.nil? release.install(Pathname.new(results[:install_dir])) else release.install(Pathname.new(installed.mod.modulepath)) end end results[:result] = :success results[:installed_modules] = releases results[:graph] = [ build_install_graph(releases.first, releases) ] rescue ModuleToolError, ForgeError => err results[:error] = { :oneline => err.message, :multiline => err.multiline, } ensure results[:result] ||= :failure end results end private def module_repository @repo ||= Puppet::Forge.new end def local_tarball_source @tarball_source ||= begin Puppet::ModuleTool::LocalTarball.new(@name) rescue Puppet::Module::Error => e raise InvalidModuleError.new(@name, :action => @action, :error => e) end end def installed_modules_source @installed ||= Puppet::ModuleTool::InstalledModules.new(@environment) end def installed_modules installed_modules_source.modules end def build_single_module_graph(name, version) range = Semantic::VersionRange.parse(version) graph = Semantic::Dependency::Graph.new(name => range) releases = Semantic::Dependency.fetch_releases(name) releases.each { |release| release.dependencies.clear } graph << releases end def build_dependency_graph(name, version) Semantic::Dependency.query(name => version) end def build_install_graph(release, installed, graphed = []) graphed << release dependencies = release.dependencies.values.map do |deps| dep = (deps & installed).first unless dep.nil? || graphed.include?(dep) build_install_graph(dep, installed, graphed) end end previous = installed_modules[release.name] previous = previous.version if previous return { :release => release, :name => release.name, :path => release.install_dir.to_s, :dependencies => dependencies.compact, :version => release.version, :previous_version => previous, :action => (previous.nil? || previous == release.version || forced? ? :install : :upgrade), } end include Puppet::ModuleTool::Shared # Return a Pathname object representing the path to the module # release package in the `Puppet.settings[:module_working_dir]`. def get_release_packages get_local_constraints if !forced? && @installed.include?(@module_name) raise AlreadyInstalledError, :module_name => @module_name, :installed_version => @installed[@module_name].first.version, :requested_version => @version || (@conditions[@module_name].empty? ? :latest : :best), :local_changes => Puppet::ModuleTool::Applications::Checksummer.run(@installed[@module_name].first.path) end if @ignore_dependencies && @source == :filesystem @urls = {} @remote = { "#{@module_name}@#{@version}" => { } } @versions = { @module_name => [ { :vstring => @version, :semver => SemVer.new(@version) } ] } else get_remote_constraints(@forge) end @graph = resolve_constraints({ @module_name => @version }) @graph.first[:tarball] = @filename if @source == :filesystem resolve_install_conflicts(@graph) unless forced? # This clean call means we never "cache" the module we're installing, but this # is desired since module authors can easily rerelease modules different content but the same # version number, meaning someone with the old content cached will be very confused as to why # they can't get new content. # Long term we should just get rid of this caching behavior and cleanup downloaded modules after they install # but for now this is a quick fix to disable caching Puppet::Forge::Cache.clean download_tarballs(@graph, @graph.last[:path], @forge) end # # Resolve installation conflicts by checking if the requested module # or one of its dependencies conflicts with an installed module. # # Conflicts occur under the following conditions: # # When installing 'puppetlabs-foo' and an existing directory in the # target install path contains a 'foo' directory and we cannot determine # the "full name" of the installed module. # # When installing 'puppetlabs-foo' and 'pete-foo' is already installed. # This is considered a conflict because 'puppetlabs-foo' and 'pete-foo' # install into the same directory 'foo'. # def resolve_install_conflicts(graph, is_dependency = false) Puppet.debug("Resolving conflicts for #{graph.map {|n| n[:module]}.join(',')}") graph.each do |release| @environment.modules_by_path[options[:target_dir]].each do |mod| if mod.has_metadata? metadata = { :name => mod.forge_name.gsub('/', '-'), :version => mod.version } next if release[:module] == metadata[:name] else metadata = nil end if release[:module] =~ /-#{mod.name}$/ dependency_info = { :name => release[:module], :version => release[:version][:vstring] } dependency = is_dependency ? dependency_info : nil all_versions = @versions["#{@module_name}"].sort_by { |h| h[:semver] } versions = all_versions.select { |x| x[:semver].special == '' } versions = all_versions if versions.empty? latest_version = versions.last[:vstring] raise InstallConflictError, :requested_module => @module_name, :requested_version => @version || "latest: v#{latest_version}", :dependency => dependency, :directory => mod.path, :metadata => metadata end end deps = release[:dependencies] if deps && !deps.empty? resolve_install_conflicts(deps, true) end end end # # Check if a file is a vaild module package. # --- # FIXME: Checking for a valid module package should be more robust and # use the actual metadata contained in the package. 03132012 - Hightower # +++ # def is_module_package?(name) filename = File.expand_path(name) filename =~ /.tar.gz$/ end end end end diff --git a/lib/puppet/module_tool/applications/upgrader.rb b/lib/puppet/module_tool/applications/upgrader.rb index d1c4a9532..bb5ac82bb 100644 --- a/lib/puppet/module_tool/applications/upgrader.rb +++ b/lib/puppet/module_tool/applications/upgrader.rb @@ -1,279 +1,277 @@ require 'pathname' require 'puppet/forge' require 'puppet/module_tool' require 'puppet/module_tool/shared_behaviors' require 'puppet/module_tool/install_directory' require 'puppet/module_tool/installed_modules' module Puppet::ModuleTool module Applications class Upgrader < Application include Puppet::ModuleTool::Errors def initialize(name, options) super(options) @action = :upgrade @environment = options[:environment_instance] @name = name @ignore_changes = forced? || options[:ignore_changes] @ignore_dependencies = forced? || options[:ignore_dependencies] Semantic::Dependency.add_source(installed_modules_source) Semantic::Dependency.add_source(module_repository) end def run name = @name.tr('/', '-') version = options[:version] || '>= 0.0.0' results = { :action => :upgrade, :requested_version => options[:version] || :latest, } begin all_modules = @environment.modules_by_path.values.flatten matching_modules = all_modules.select do |x| x.forge_name && x.forge_name.tr('/', '-') == name end if matching_modules.empty? raise NotInstalledError, results.merge(:module_name => name) elsif matching_modules.length > 1 raise MultipleInstalledError, results.merge(:module_name => name, :installed_modules => matching_modules) end installed_release = installed_modules[name] # `priority` is an attribute of a `Semantic::Dependency::Source`, # which is delegated through `ModuleRelease` instances for the sake of # comparison (sorting). By default, the `InstalledModules` source has # a priority of 10 (making it the most preferable source, so that # already installed versions of modules are selected in preference to # modules from e.g. the Forge). Since we are specifically looking to # upgrade this module, we don't want the installed version of this # module to be chosen in preference to those with higher versions. # # This implementation is suboptimal, and since we can expect this sort # of behavior to be reasonably common in Semantic, we should probably # see about implementing a `ModuleRelease#override_priority` method # (or something similar). def installed_release.priority 0 end mod = installed_release.mod results[:installed_version] = Semantic::Version.parse(mod.version) dir = Pathname.new(mod.modulepath) vstring = mod.version ? "v#{mod.version}" : '???' Puppet.notice "Found '#{name}' (#{colorize(:cyan, vstring)}) in #{dir} ..." unless @ignore_changes changes = Checksummer.run(mod.path) rescue [] if mod.has_metadata? && !changes.empty? raise LocalChangesError, :action => :upgrade, :module_name => name, :requested_version => results[:requested_version], :installed_version => mod.version end end Puppet::Forge::Cache.clean # Ensure that there is at least one candidate release available # for the target package. available_versions = module_repository.fetch(name) if available_versions.empty? raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host) elsif results[:requested_version] != :latest requested = Semantic::VersionRange.parse(results[:requested_version]) unless available_versions.any? {|m| requested.include? m.version} raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host) end end Puppet.notice "Downloading from #{module_repository.host} ..." if @ignore_dependencies graph = build_single_module_graph(name, version) else graph = build_dependency_graph(name, version) end unless forced? add_module_name_constraints_to_graph(graph) end installed_modules.each do |installed_module, release| installed_module = installed_module.tr('/', '-') next if installed_module == name version = release.version unless forced? # Since upgrading already installed modules can be troublesome, # we'll place constraints on the graph for each installed # module, locking it to upgrades within the same major version. - ">=#{version} #{version.major}.x".tap do |range| - graph.add_constraint('installed', installed_module, range) do |node| - Semantic::VersionRange.parse(range).include? node.version - end + installed_range = ">=#{version} #{version.major}.x" + graph.add_constraint('installed', installed_module, installed_range) do |node| + Semantic::VersionRange.parse(installed_range).include? node.version end release.mod.dependencies.each do |dep| dep_name = dep['name'].tr('/', '-') - dep['version_requirement'].tap do |range| - graph.add_constraint("#{installed_module} constraint", dep_name, range) do |node| - Semantic::VersionRange.parse(range).include? node.version - end + range = dep['version_requirement'] + graph.add_constraint("#{installed_module} constraint", dep_name, range) do |node| + Semantic::VersionRange.parse(range).include? node.version end end end end begin Puppet.info "Resolving dependencies ..." releases = Semantic::Dependency.resolve(graph) rescue Semantic::Dependency::UnsatisfiableGraph raise NoVersionsSatisfyError, results.merge(:requested_name => name) end releases.each do |rel| if mod = installed_modules_source.by_name[rel.name.split('-').last] next if mod.has_metadata? && mod.forge_name.tr('/', '-') == rel.name if rel.name != name dependency = { :name => rel.name, :version => rel.version } end raise InstallConflictError, :requested_module => name, :requested_version => options[:version] || 'latest', :dependency => dependency, :directory => mod.path, :metadata => mod.metadata end end child = releases.find { |x| x.name == name } unless forced? if child.version == results[:installed_version] versions = graph.dependencies[name].map { |r| r.version } newer_versions = versions.select { |v| v > results[:installed_version] } raise VersionAlreadyInstalledError, :module_name => name, :requested_version => results[:requested_version], :installed_version => results[:installed_version], :newer_versions => newer_versions, :possible_culprits => installed_modules_source.fetched.reject { |x| x == name } elsif child.version < results[:installed_version] raise DowngradingUnsupportedError, :module_name => name, :requested_version => results[:requested_version], :installed_version => results[:installed_version] end end Puppet.info "Preparing to upgrade ..." releases.each { |release| release.prepare } Puppet.notice 'Upgrading -- do not interrupt ...' releases.each do |release| if installed = installed_modules[release.name] release.install(Pathname.new(installed.mod.modulepath)) else release.install(dir) end end results[:result] = :success results[:base_dir] = releases.first.install_dir results[:affected_modules] = releases results[:graph] = [ build_install_graph(releases.first, releases) ] rescue VersionAlreadyInstalledError => e results[:result] = (e.newer_versions.empty? ? :noop : :failure) results[:error] = { :oneline => e.message, :multiline => e.multiline } rescue => e results[:error] = { :oneline => e.message, :multiline => e.respond_to?(:multiline) ? e.multiline : [e.to_s, e.backtrace].join("\n") } ensure results[:result] ||= :failure end results end private def module_repository @repo ||= Puppet::Forge.new end def installed_modules_source @installed ||= Puppet::ModuleTool::InstalledModules.new(@environment) end def installed_modules installed_modules_source.modules end def build_single_module_graph(name, version) range = Semantic::VersionRange.parse(version) graph = Semantic::Dependency::Graph.new(name => range) releases = Semantic::Dependency.fetch_releases(name) releases.each { |release| release.dependencies.clear } graph << releases end def build_dependency_graph(name, version) Semantic::Dependency.query(name => version) end def build_install_graph(release, installed, graphed = []) previous = installed_modules[release.name] previous = previous.version if previous action = :upgrade unless previous && previous != release.version action = :install end graphed << release dependencies = release.dependencies.values.map do |deps| dep = (deps & installed).first if dep == installed_modules[dep.name] next end if dep && !graphed.include?(dep) build_install_graph(dep, installed, graphed) end end.compact return { :release => release, :name => release.name, :path => release.install_dir, :dependencies => dependencies.compact, :version => release.version, :previous_version => previous, :action => action, } end include Puppet::ModuleTool::Shared end end end diff --git a/lib/puppet/module_tool/contents_description.rb b/lib/puppet/module_tool/contents_description.rb index ca67930d8..6b443c3c4 100644 --- a/lib/puppet/module_tool/contents_description.rb +++ b/lib/puppet/module_tool/contents_description.rb @@ -1,84 +1,88 @@ require 'puppet/module_tool' module Puppet::ModuleTool # = ContentsDescription # # This class populates +Metadata+'s Puppet type information. class ContentsDescription # Instantiate object for string +module_path+. def initialize(module_path) @module_path = module_path end # Update +Metadata+'s Puppet type information. def annotate(metadata) metadata.types.replace data.clone end # Return types for this module. Result is an array of hashes, each of which # describes a Puppet type. The type description hash structure is: # * :name => Name of this Puppet type. # * :doc => Documentation for this type. # * :properties => Array of hashes representing the type's properties, each # containing :name and :doc. # * :parameters => Array of hashes representing the type's parameters, each # containing :name and :doc. # * :providers => Array of hashes representing the types providers, each # containing :name and :doc. # TODO Write a TypeDescription to encapsulate these structures and logic? def data unless @data @data = [] type_names = [] for module_filename in Dir[File.join(@module_path, "lib/puppet/type/*.rb")] require module_filename type_name = File.basename(module_filename, ".rb") type_names << type_name for provider_filename in Dir[File.join(@module_path, "lib/puppet/provider/#{type_name}/*.rb")] require provider_filename end end type_names.each do |name| if type = Puppet::Type.type(name.to_sym) type_hash = {:name => name, :doc => type.doc} type_hash[:properties] = attr_doc(type, :property) type_hash[:parameters] = attr_doc(type, :param) if type.providers.size > 0 type_hash[:providers] = provider_doc(type) end @data << type_hash else Puppet.warning "Could not find/load type: #{name}" end end end @data end # Return an array of hashes representing this +type+'s attrs of +kind+ # (e.g. :param or :property), each containing :name and :doc. def attr_doc(type, kind) - [].tap do |attrs| - type.allattrs.each do |name| - if type.attrtype(name) == kind && name != :provider - attrs.push(:name => name, :doc => type.attrclass(name).doc) - end + attrs = [] + + type.allattrs.each do |name| + if type.attrtype(name) == kind && name != :provider + attrs.push(:name => name, :doc => type.attrclass(name).doc) end end + + attrs end # Return an array of hashes representing this +type+'s providers, each # containing :name and :doc. def provider_doc(type) - [].tap do |providers| - type.providers.sort.each do |prov| - providers.push(:name => prov, :doc => type.provider(prov).doc) - end + providers = [] + + type.providers.sort.each do |prov| + providers.push(:name => prov, :doc => type.provider(prov).doc) end + + providers end end end diff --git a/lib/puppet/module_tool/installed_modules.rb b/lib/puppet/module_tool/installed_modules.rb index 8042f09b8..b7bdc622b 100644 --- a/lib/puppet/module_tool/installed_modules.rb +++ b/lib/puppet/module_tool/installed_modules.rb @@ -1,98 +1,96 @@ require 'pathname' require 'puppet/forge' require 'puppet/module_tool' module Puppet::ModuleTool class InstalledModules < Semantic::Dependency::Source attr_reader :modules, :by_name def priority 10 end def initialize(env) @env = env modules = env.modules_by_path @fetched = [] @modules = {} @by_name = {} env.modulepath.each do |path| modules[path].each do |mod| @by_name[mod.name] = mod next unless mod.has_metadata? release = ModuleRelease.new(self, mod) @modules[release.name] ||= release end end @modules.freeze end # Fetches {ModuleRelease} entries for each release of the named module. # # @param name [String] the module name to look up # @return [Array] a list of releases for # the given name # @see Semantic::Dependency::Source#fetch def fetch(name) name = name.tr('/', '-') if @modules.key? name @fetched << name [ @modules[name] ] else [ ] end end def fetched @fetched end class ModuleRelease < Semantic::Dependency::ModuleRelease attr_reader :mod, :metadata def initialize(source, mod) @mod = mod @metadata = mod.metadata name = mod.forge_name.tr('/', '-') begin version = Semantic::Version.parse(mod.version) rescue Semantic::Version::ValidationFailure => e Puppet.warning "#{mod.name} (#{mod.path}) has an invalid version number (#{mod.version}). The version has been set to 0.0.0. If you are the maintainer for this module, please update the metadata.json with a valid Semantic Version (http://semver.org)." version = Semantic::Version.parse("0.0.0") end release = "#{name}@#{version}" super(source, name, version, {}) if mod.dependencies mod.dependencies.each do |dependency| results = Puppet::ModuleTool.parse_module_dependency(release, dependency) dep_name, parsed_range, range = results - dependency.tap do |dep| - add_constraint('initialize', dep_name, range.to_s) do |node| - parsed_range === node.version - end + add_constraint('initialize', dep_name, range.to_s) do |node| + parsed_range === node.version end end end end def install_dir Pathname.new(@mod.path).dirname end def install(dir) # If we're already installed, there's no need for us to faff about. end def prepare # We're already installed; what preparation remains? end end end end