lib/berkshelf/lockfile.rb in berkshelf-3.0.0.beta9 vs lib/berkshelf/lockfile.rb in berkshelf-3.0.0.rc1
- old
+ new
@@ -96,12 +96,10 @@
checked = {}
berksfile.dependencies.each do |dependency|
Berkshelf.log.debug "Checking #{dependency}"
- checked[dependency.name] = true
-
locked = find(dependency)
if locked.nil?
Berkshelf.log.debug " Not in lockfile - cannot be trusted!"
return false
end
@@ -150,21 +148,40 @@
# the graph item to check transitive dependencies for
# @param [Hash] checked
# the list of already checked dependencies
#
# @return [Boolean]
- def satisfies_transitive?(graph_item, checked)
- graph_item.dependencies.all? do |name, constraint|
- return true if checked[name]
+ def satisfies_transitive?(graph_item, checked, level = 0)
+ indent = ' '*(level + 2)
- checked[name] = true
+ Berkshelf.log.debug "#{indent}Checking transitive dependencies for #{graph_item}"
+ if checked[graph_item.name]
+ Berkshelf.log.debug "#{indent} Already checked - skipping"
+ return true
+ end
+
+ graph_item.dependencies.each do |name, constraint|
+ Berkshelf.log.debug "#{indent} Checking #{name} (#{constraint})"
+
graphed = graph.find(name)
- return false if graphed.nil?
+ if graphed.nil?
+ Berkshelf.log.debug "#{indent} Not graphed - cannot be satisifed"
+ return false
+ end
- Semverse::Constraint.new(constraint).satisfies?(graphed.version) &&
- satisfies_transitive?(graphed, checked)
+ unless Semverse::Constraint.new(constraint).satisfies?(graphed.version)
+ Berkshelf.log.debug "#{indent} Version constraint is not satisfied"
+ return false
+ end
+
+ unless satisfies_transitive?(graphed, checked, level + 2)
+ Berkshelf.log.debug "#{indent} Transitive are not satisifed"
+ return false
+ end
+
+ checked[name] = true
end
end
# Resolve this Berksfile and apply the locks found in the generated
# +Berksfile.lock+ to the target Chef environment
@@ -286,20 +303,28 @@
#
# This method first removes the dependency from the list of top-level
# dependencies. Then it uses a recursive algorithm to safely remove any
# other dependencies from the graph that are no longer needed.
#
- # @raise [CookbookNotFound]
- # if the provided dependency does not exist
- #
# @param [String] dependency
# the name of the cookbook to remove
- def unlock(dependency)
+ def unlock(dependency, force = false)
@dependencies.delete(Dependency.name(dependency))
- graph.remove(dependency)
+
+ if force
+ graph.remove(dependency, ignore: graph.locks.keys)
+ else
+ graph.remove(dependency)
+ end
end
+ # Completely remove all dependencies from the lockfile and underlying graph.
+ def unlock_all
+ @dependencies = {}
+ @graph = Graph.new(self)
+ end
+
# Iterate over each top-level dependency defined in the lockfile and
# check if that dependency is still defined in the Berksfile.
#
# If the dependency is no longer present in the Berksfile, it is "safely"
# removed using {Lockfile#unlock} and {Lockfile#remove}. This prevents
@@ -315,62 +340,76 @@
# if the constraint exists, but is no longer satisifed by the existing
# locked version
#
# @return [Array<Dependency>]
def reduce!
- # Store a list of cookbooks to ungraph
- to_ungraph = {}
- to_ignore = {}
+ Berkshelf.log.info "Reducing lockfile"
+ Berkshelf.log.debug "Current lockfile:"
+ Berkshelf.log.debug ""
+ to_lock.each_line do |line|
+ Berkshelf.log.debug " #{line.chomp}"
+ end
+ Berkshelf.log.debug ""
+
+
# Unlock any locked dependencies that are no longer in the Berksfile
+ Berkshelf.log.debug "Unlocking dependencies no longer in the Berksfile"
+
dependencies.each do |dependency|
- unless berksfile.has_dependency?(dependency.name)
- unlock(dependency)
+ Berkshelf.log.debug " Checking #{dependency}"
- # Keep a record. We know longer trust these dependencies, but simply
- # unlocking them does not guarantee their removal from the graph.
- # Instead, we keep a record of the dependency to unlock it later (in
- # case it is actually removable because it's parent requirer is also
- # being removed in this reduction). It's a form of science. Don't
- # question it too much.
- to_ungraph[dependency.name] = true
- to_ignore[dependency.name] = true
+ if berksfile.has_dependency?(dependency.name)
+ Berkshelf.log.debug " Skipping unlock for #{dependency.name} (exists in the Berksfile)"
+ else
+ Berkshelf.log.debug " Unlocking #{dependency.name}"
+ unlock(dependency, true)
end
end
# Remove any transitive dependencies
+ Berkshelf.log.debug "Removing transitive dependencies"
+
berksfile.dependencies.each do |dependency|
+ Berkshelf.log.debug " Checking #{dependency}"
+
graphed = graph.find(dependency)
- next if graphed.nil?
+ if graphed.nil?
+ Berkshelf.log.debug " Skipping (not graphed)"
+ next
+ end
+
unless dependency.version_constraint.satisfies?(graphed.version)
+ Berkshelf.log.debug " Constraints are not satisfied!"
raise OutdatedDependency.new(graphed, dependency)
end
if cookbook = dependency.cached_cookbook
+ Berkshelf.log.debug " Cached cookbook exists"
+ Berkshelf.log.debug " Checking dependencies on the cached cookbook"
+
graphed.dependencies.each do |name, constraint|
+ Berkshelf.log.debug " Checking #{name} (#{constraint})"
+
# Unless the cookbook still depends on this key, we want to queue it
# for unlocking. This is the magic that prevents transitive
# dependency leaking.
unless cookbook.dependencies.has_key?(name)
- to_ungraph[name] = true
-
- # We also want to ignore the top-level dependency. We can no
- # longer trust the graph that we have been given for that
- # dependency and therefore need to reduce it.
- to_ignore[dependency.name] = true
+ Berkshelf.log.debug " Not found!"
+ unlock(name, true)
end
end
end
end
- # Now remove all the unlockable items
- ignore = to_ungraph.merge(to_ignore).keys
-
- to_ungraph.each do |name, _|
- graph.remove(name, ignore: ignore)
+ Berkshelf.log.debug "New lockfile:"
+ Berkshelf.log.debug ""
+ to_lock.each_line do |line|
+ Berkshelf.log.debug " #{line.chomp}"
end
+ Berkshelf.log.debug ""
end
# Write the contents of the current statue of the lockfile to disk. This
# method uses an atomic file write. A temporary file is created, written,
@@ -564,10 +603,14 @@
def locks
@graph.sort.inject({}) do |hash, (name, item)|
dependency = @lockfile.find(name) ||
@berksfile && @berksfile.find(name) ||
Dependency.new(@berksfile, name)
+
+ # We need to make a copy of the dependency, or else we could be
+ # modifying an existing object that other processes depend on!
+ dependency = dependency.dup
dependency.locked_version = item.version
hash[item.name] = dependency
hash
end
@@ -745,9 +788,14 @@
# the name to use
# @param [#to_s] constraint
# the version constraint to use
def add_dependency(name, constraint)
@dependencies[name.to_s] = constraint.to_s
+ end
+
+ # @private
+ def to_s
+ "#{name} (#{version})"
end
end
end
end
end