class Puppet::ModuleTool::Applications::Upgrader

Public Class Methods

new(name, options) click to toggle source
   # File lib/puppet/module_tool/applications/upgrader.rb
16 def initialize(name, options)
17   super(options)
18 
19   @action              = :upgrade
20   @environment         = options[:environment_instance]
21   @name                = name
22   @ignore_changes      = forced? || options[:ignore_changes]
23   @ignore_dependencies = forced? || options[:ignore_dependencies]
24 
25   SemanticPuppet::Dependency.add_source(installed_modules_source)
26   SemanticPuppet::Dependency.add_source(module_repository)
27 end

Public Instance Methods

run() click to toggle source
    # File lib/puppet/module_tool/applications/upgrader.rb
 29 def run
 30   # Disallow anything that invokes md5 to avoid un-friendly termination due to FIPS
 31   raise _("Module upgrade is prohibited in FIPS mode.") if Puppet.runtime[:facter].value(:fips_enabled)
 32 
 33   name = @name.tr('/', '-')
 34   version = options[:version] || '>= 0.0.0'
 35 
 36   results = {
 37     :action => :upgrade,
 38     :requested_version => options[:version] || :latest,
 39   }
 40 
 41   begin
 42     all_modules = @environment.modules_by_path.values.flatten
 43     matching_modules = all_modules.select do |x|
 44       x.forge_name && x.forge_name.tr('/', '-') == name
 45     end
 46 
 47     if matching_modules.empty?
 48       raise NotInstalledError, results.merge(:module_name => name)
 49     elsif matching_modules.length > 1
 50       raise MultipleInstalledError, results.merge(:module_name => name, :installed_modules => matching_modules)
 51     end
 52 
 53     installed_release = installed_modules[name]
 54 
 55     # `priority` is an attribute of a `SemanticPuppet::Dependency::Source`,
 56     # which is delegated through `ModuleRelease` instances for the sake of
 57     # comparison (sorting). By default, the `InstalledModules` source has
 58     # a priority of 10 (making it the most preferable source, so that
 59     # already installed versions of modules are selected in preference to
 60     # modules from e.g. the Forge). Since we are specifically looking to
 61     # upgrade this module, we don't want the installed version of this
 62     # module to be chosen in preference to those with higher versions.
 63     #
 64     # This implementation is suboptimal, and since we can expect this sort
 65     # of behavior to be reasonably common in Semantic, we should probably
 66     # see about implementing a `ModuleRelease#override_priority` method
 67     # (or something similar).
 68     def installed_release.priority
 69       0
 70     end
 71 
 72     mod = installed_release.mod
 73     results[:installed_version] = SemanticPuppet::Version.parse(mod.version)
 74     dir = Pathname.new(mod.modulepath)
 75 
 76     vstring = mod.version ? "v#{mod.version}" : '???'
 77     Puppet.notice _("Found '%{name}' (%{version}) in %{dir} ...") % { name: name, version: colorize(:cyan, vstring), dir: dir }
 78     unless @ignore_changes
 79       changes = Checksummer.run(mod.path) rescue []
 80       if mod.has_metadata? && !changes.empty?
 81         raise LocalChangesError,
 82           :action            => :upgrade,
 83           :module_name       => name,
 84           :requested_version => results[:requested_version],
 85           :installed_version => mod.version
 86       end
 87     end
 88 
 89     Puppet::Forge::Cache.clean
 90 
 91     # Ensure that there is at least one candidate release available
 92     # for the target package.
 93     available_versions = module_repository.fetch(name)
 94     if available_versions.empty?
 95       raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host)
 96     elsif results[:requested_version] != :latest
 97       requested = Puppet::Module.parse_range(results[:requested_version])
 98       unless available_versions.any? {|m| requested.include? m.version}
 99         raise NoCandidateReleasesError, results.merge(:module_name => name, :source => module_repository.host)
100       end
101     end
102 
103     Puppet.notice _("Downloading from %{host} ...") % { host: module_repository.host }
104     if @ignore_dependencies
105       graph = build_single_module_graph(name, version)
106     else
107       graph = build_dependency_graph(name, version)
108     end
109 
110     unless forced?
111       add_module_name_constraints_to_graph(graph)
112     end
113 
114     installed_modules.each do |installed_module, release|
115       installed_module = installed_module.tr('/', '-')
116       next if installed_module == name
117 
118       version = release.version
119 
120       unless forced?
121         # Since upgrading already installed modules can be troublesome,
122         # we'll place constraints on the graph for each installed
123         # module, locking it to upgrades within the same major version.
124         installed_range = ">=#{version} #{version.major}.x"
125         graph.add_constraint('installed', installed_module, installed_range) do |node|
126           Puppet::Module.parse_range(installed_range).include? node.version
127         end
128 
129         release.mod.dependencies.each do |dep|
130           dep_name = dep['name'].tr('/', '-')
131 
132           range = dep['version_requirement']
133           graph.add_constraint("#{installed_module} constraint", dep_name, range) do |node|
134             Puppet::Module.parse_range(range).include? node.version
135           end
136         end
137       end
138     end
139 
140     begin
141       Puppet.info _("Resolving dependencies ...")
142       releases = SemanticPuppet::Dependency.resolve(graph)
143     rescue SemanticPuppet::Dependency::UnsatisfiableGraph
144       raise NoVersionsSatisfyError, results.merge(:requested_name => name)
145     end
146 
147     releases.each do |rel|
148       mod = installed_modules_source.by_name[rel.name.split('-').last]
149       if mod
150         next if mod.has_metadata? && mod.forge_name.tr('/', '-') == rel.name
151 
152         if rel.name != name
153           dependency = {
154             :name => rel.name,
155             :version => rel.version
156           }
157         end
158 
159         raise InstallConflictError,
160           :requested_module  => name,
161           :requested_version => options[:version] || 'latest',
162           :dependency        => dependency,
163           :directory         => mod.path,
164           :metadata          => mod.metadata
165       end
166     end
167 
168     child = releases.find { |x| x.name == name }
169 
170     unless forced?
171       if child.version == results[:installed_version]
172         versions = graph.dependencies[name].map { |r| r.version }
173         newer_versions = versions.select { |v| v > results[:installed_version] }
174 
175         raise VersionAlreadyInstalledError,
176           :module_name       => name,
177           :requested_version => results[:requested_version],
178           :installed_version => results[:installed_version],
179           :newer_versions    => newer_versions,
180           :possible_culprits => installed_modules_source.fetched.reject { |x| x == name }
181       elsif child.version < results[:installed_version]
182         raise DowngradingUnsupportedError,
183           :module_name       => name,
184           :requested_version => results[:requested_version],
185           :installed_version => results[:installed_version]
186       end
187     end
188 
189     Puppet.info _("Preparing to upgrade ...")
190     releases.each { |release| release.prepare }
191 
192     Puppet.notice _('Upgrading -- do not interrupt ...')
193     releases.each do |release|
194       installed = installed_modules[release.name]
195       if installed
196         release.install(Pathname.new(installed.mod.modulepath))
197       else
198         release.install(dir)
199       end
200     end
201 
202     results[:result] = :success
203     results[:base_dir] = releases.first.install_dir
204     results[:affected_modules] = releases
205     results[:graph] = [ build_install_graph(releases.first, releases) ]
206 
207   rescue VersionAlreadyInstalledError => e
208     results[:result] = (e.newer_versions.empty? ? :noop : :failure)
209     results[:error] = { :oneline => e.message, :multiline => e.multiline }
210   rescue => e
211     results[:error] = {
212       :oneline   => e.message,
213       :multiline => e.respond_to?(:multiline) ? e.multiline : [e.to_s, e.backtrace].join("\n")
214     }
215   ensure
216     results[:result] ||= :failure
217   end
218 
219   results
220 end

Private Instance Methods

build_dependency_graph(name, version) click to toggle source
    # File lib/puppet/module_tool/applications/upgrader.rb
243 def build_dependency_graph(name, version)
244   SemanticPuppet::Dependency.query(name => version)
245 end
build_install_graph(release, installed, graphed = []) click to toggle source
    # File lib/puppet/module_tool/applications/upgrader.rb
247 def build_install_graph(release, installed, graphed = [])
248   previous = installed_modules[release.name]
249   previous = previous.version if previous
250 
251   action = :upgrade
252   unless previous && previous != release.version
253     action = :install
254   end
255 
256   graphed << release
257 
258   dependencies = release.dependencies.values.map do |deps|
259     dep = (deps & installed).first
260     if dep == installed_modules[dep.name]
261       next
262     end
263 
264     if dep && !graphed.include?(dep)
265       build_install_graph(dep, installed, graphed)
266     end
267   end.compact
268 
269   return {
270     :release          => release,
271     :name             => release.name,
272     :path             => release.install_dir,
273     :dependencies     => dependencies.compact,
274     :version          => release.version,
275     :previous_version => previous,
276     :action           => action,
277   }
278 end
build_single_module_graph(name, version) click to toggle source
    # File lib/puppet/module_tool/applications/upgrader.rb
235 def build_single_module_graph(name, version)
236   range = Puppet::Module.parse_range(version)
237   graph = SemanticPuppet::Dependency::Graph.new(name => range)
238   releases = SemanticPuppet::Dependency.fetch_releases(name)
239   releases.each { |release| release.dependencies.clear }
240   graph << releases
241 end
installed_modules() click to toggle source
    # File lib/puppet/module_tool/applications/upgrader.rb
231 def installed_modules
232   installed_modules_source.modules
233 end
installed_modules_source() click to toggle source
    # File lib/puppet/module_tool/applications/upgrader.rb
227 def installed_modules_source
228   @installed ||= Puppet::ModuleTool::InstalledModules.new(@environment)
229 end
module_repository() click to toggle source
    # File lib/puppet/module_tool/applications/upgrader.rb
223 def module_repository
224   @repo ||= Puppet::Forge.new(Puppet[:module_repository])
225 end