class Puppet::ModuleTool::Applications::Upgrader

Public Class Methods

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

Public Instance Methods

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

Private Instance Methods

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