Module: Ra10ke::Solve

Included in:
RakeTask
Defined in:
lib/ra10ke/solve.rb

Instance Method Summary collapse

Instance Method Details

#define_task_solve_dependencies(*_args) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ra10ke/solve.rb', line 15

def define_task_solve_dependencies(*_args)
  desc 'Find missing or outdated module dependencies'
  task :solve_dependencies, [:allow_major_bump] do |_t, args|
    require 'r10k/puppetfile'
    require 'r10k/module/git'
    require 'r10k/module/metadata_file'
    require 'puppet_forge'

    allow_major_bump = false
    allow_major_bump = true if args[:allow_major_bump]

    # Same as in the dependencies task, but oh well.
    PuppetForge.user_agent = "ra10ke/#{Ra10ke::VERSION}"
    puppetfile = get_puppetfile
    puppetfile.load!
    PuppetForge.host = puppetfile.forge if /^http/.match?(puppetfile.forge)

    # ignore file allows for "don't tell me about this"
    ignore_modules = []
    ignore_modules = File.readlines('.r10kignore').each(&:chomp!) if File.exist?('.r10kignore')
    # Actual new logic begins here:
    cache = ENV['XDG_CACHE_DIR'] || File.expand_path('~/.cache')

    FileUtils.mkdir_p(cache)

    # Metadata cache, since the Forge is slow:
    @metadata_cache = YAML::Store.new File.join(cache, 'ra10ke.metadata_cache')
    # The graph of available module versions
    @graph = Solve::Graph.new
    # Set of modules that we have already added to the graph
    @processed_modules = Set.new
    # The set of "demands" we make of the solver. Will be a list of module names
    # Could also demand certain version constraints to hold, but the code does not do it
    # Can be either "module-name" or ["module-name", "version-constraint"]
    @demands = Set.new
    # List of modules we have in the Puppetfile, as [name, version] pairs
    @current_modules = []

    # Single pass: partition modules into git/Forge arrays and simultaneously seed
    # @processed_modules with all git module names. Seeding upfront prevents
    # add_reqs_to_graph from fetching Forge releases for any git module encountered
    # as a transitive dependency, regardless of declaration order in the Puppetfile.
    git_modules = []
    forge_modules = []
    puppetfile.modules.each do |puppet_module|
      next if ignore_modules.include? puppet_module.title

      if puppet_module.instance_of?(R10K::Module::Forge)
        forge_modules << puppet_module
      elsif puppet_module.instance_of?(R10K::Module::Git)
        @processed_modules.add(puppet_module.title.tr('/', '-'))
        git_modules << puppet_module
      end
    end

    # Process git modules first so their metadata populates the graph before
    # any Forge module's transitive dependencies are resolved.
    git_modules.each do |puppet_module|
      # This downloads the git module to modules/modulename
      meta = (puppet_module)
      version = get_key_or_sym(meta, :version)
      module_name = puppet_module.title.tr('/', '-')
      @current_modules << [module_name, version]
      # We should add git modules with exact versions, or the system might recommend updating to a
      # Forge version.
      puts "Adding git module #{module_name} to the list of required modules with exact version: #{version}"
      @demands.add([module_name, version])
      mod = @graph.artifact(module_name, version)
      puts "...Adding requirements for git module #{module_name}-#{version}"
      add_reqs_to_graph(mod, meta)
    end

    # Process Forge modules. Any git module already in @processed_modules will
    # be skipped by add_reqs_to_graph when encountered as a transitive dependency.
    forge_modules.each do |puppet_module|
      module_name = puppet_module.title.tr('/', '-')
      installed_version = puppet_module.expected_version
      puts "Processing Forge module #{module_name}-#{installed_version}"
      @current_modules << [module_name, installed_version]
      @graph.artifact(module_name, installed_version)
      constraint = '>=0.0.0'
      unless allow_major_bump
        ver = Semverse::Version.new installed_version
        if ver.major.zero?
          constraint = "~>#{installed_version}"
        else
          nver = Semverse::Version.new([ver.major + 1, 0, 0])
          constraint = "<#{nver}"
        end
      end
      puts "...Adding a demand: #{module_name} #{constraint}"

      @demands.add([module_name, constraint])
      puts '...Fetching latest release version information'
      forge_rel = PuppetForge::Module.find(module_name).current_release
      mod = @graph.artifact(module_name, forge_rel.version)
      puts '...Adding its requirements to the graph'
      meta = (module_name, forge_rel)
      add_reqs_to_graph(mod, meta)
    end
    puts
    puts 'Resolving dependencies...'
    puts 'WARNING:  Potentially breaking updates are allowed for this resolution' if allow_major_bump
    result = Solve.it!(@graph, @demands, sorted: true)
    puts
    print_module_diff(@current_modules, result)
  end
end