#!/usr/bin/env ruby require 'puppet_metadata' require 'semantic_puppet' def normalize_name(name) name.tr('-', '/') end class LocalModules < SemanticPuppet::Dependency::Source attr_reader :modules def initialize(module_dirs) @modules = {} module_dirs.each do |module_dir| Dir[File.join(module_dir, '*', 'metadata.json')].sort.map do |metadata| mod = PuppetMetadata.read(metadata) dependencies = mod.dependencies.map { |name, requirement| [normalize_name(name), requirement.to_s] } name = normalize_name(mod.name) @modules[name] = create_release(name, mod.version, dependencies) end end @modules.freeze end def fetch(name) if @modules.key?(name) [@modules[name]] else [] end end end class ProvidedModules < SemanticPuppet::Dependency::Source attr_reader :modules def initialize(modules) @modules = {} modules.each do |name, version| name = normalize_name(name) @modules[name] = create_release(name, version) end @modules.freeze end def fetch(name) if @modules.key?(name) [@modules[name]] else [] end end end local_source = LocalModules.new(['modules']) SemanticPuppet::Dependency.add_source(local_source) # TODO: Read this from a file # These should be soft dependencies provided = { 'herculesteam/augeasproviders_core' => '2.1.0', # puppet/redis 'herculesteam/augeasproviders_sysctl' => '2.1.0', # puppet/redis 'puppet/archive' => '4.0.0', # puppet/grafana 'puppet/zypprepo' => '3.1.0', # icinga/icinga 'puppetlabs/firewall' => '2.0.0', # puppetlabs/puppetdb 'puppetlabs/vcsrepo' => '3.0.0', # icinga/icingaweb2 } provided_source = ProvidedModules.new(provided) SemanticPuppet::Dependency.add_source(provided_source) modules = Hash[local_source.modules.map { |name, mod| [name, mod.version.to_s] }] graph = SemanticPuppet::Dependency.query(modules) begin releases = SemanticPuppet::Dependency.resolve(graph) puts "Satisfied all dependencies:" releases.each do |release| # TODO: print source puts " #{release.name} #{release.version}" end rescue SemanticPuppet::Dependency::UnsatisfiableGraph => e # There was some dependency that wasn't matched. We know its name and that # it's a dependency we requested previously. Now to investigate why to # provide useful hints # TODO: it's *very* weird to store unsatisfiable in a module # Strange API unsatisfiable = SemanticPuppet::Dependency.unsatisfiable unless unsatisfiable # In older semantic_puppet unsatisfiable was unreliable # Needs https://github.com/puppetlabs/semantic_puppet/pull/37 $stderr.puts e $stderr.puts "semantic_puppet provides insufficient information to hint why" exit 1 end $stderr.puts "Unable to satisfy #{unsatisfiable}" releases = graph.dependencies[unsatisfiable] if releases.empty? # This should never happen $stderr.puts "No releases found for #{unsatisfiable}" exit 2 end releases.each do |release| $stderr.puts "Investigating version #{release.version}" unsatisfied = release.dependencies.select { |_, candidates| candidates.empty? } if unsatisfied.empty? # Is this ever the case, did we miss anything else? $stderr.puts " All dependencies satisfied" else unsatisfied.each do |dependency, _| constraint = release.constraints_for(dependency).find { |constraint| constraint[:source] == 'initialize' } $stderr.puts " Dependency #{dependency} (#{constraint[:description]}) can't be satified" constraint_graph = SemanticPuppet::Dependency.query(dependency => '>= 0') begin releases = SemanticPuppet::Dependency.resolve(constraint_graph) mod_releases = releases.select { |release| release.name == dependency } $stderr.puts " Available versions: #{mod_releases.map { |r| r.version }.join(', ')}" rescue SemanticPuppet::Dependency::UnsatisfiableGraph $stderr.puts " Unable to find any release" end end end end end