Created
April 2, 2021 10:27
-
-
Save ekohl/055633dfd53e69af8342bf325a1771eb to your computer and use it in GitHub Desktop.
A script to check Puppet dependencies based on semantic_puppet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment