lib/berkshelf/installer.rb in berkshelf-3.0.0.beta6 vs lib/berkshelf/installer.rb in berkshelf-3.0.0.beta7
- old
+ new
@@ -1,173 +1,162 @@
require 'berkshelf/api-client'
module Berkshelf
class Installer
- extend Forwardable
-
attr_reader :berksfile
+ attr_reader :lockfile
attr_reader :downloader
- def_delegator :berksfile, :lockfile
-
# @param [Berkshelf::Berksfile] berksfile
def initialize(berksfile)
@berksfile = berksfile
+ @lockfile = berksfile.lockfile
@downloader = Downloader.new(berksfile)
end
def build_universe
berksfile.sources.collect do |source|
Thread.new do
begin
+ Berkshelf.formatter.msg("Fetching cookbook index from #{source.uri}...")
source.build_universe
rescue Berkshelf::APIClientError => ex
Berkshelf.formatter.warn "Error retrieving universe from source: #{source}"
Berkshelf.formatter.warn " * [#{ex.class}] #{ex}"
end
end
end.map(&:join)
end
- # @option options [Array<String>, String] cookbooks
- #
# @return [Array<Berkshelf::CachedCookbook>]
- def run(options = {})
- dependencies = lockfile_reduce(berksfile.dependencies(options.slice(:except, :only)))
- resolver = Resolver.new(berksfile, dependencies)
- lock_deps = []
+ def run
+ reduce_lockfile!
- dependencies.each do |dependency|
- if dependency.scm_location?
- Berkshelf.formatter.fetch(dependency)
- downloader.download(dependency)
- end
+ cookbooks = if lockfile.trusted?
+ install_from_lockfile
+ else
+ install_from_universe
+ end
- next if (cookbook = dependency.cached_cookbook).nil?
+ lockfile.graph.update(cookbooks)
+ lockfile.update(berksfile.dependencies)
+ lockfile.save
- resolver.add_explicit_dependencies(cookbook)
- end
+ cookbooks
+ end
- Berkshelf.formatter.msg("building universe...")
- build_universe
+ # Install all the dependencies from the lockfile graph.
+ #
+ # @return [Array<CachedCookbook>]
+ # the list of installed cookbooks
+ def install_from_lockfile
+ locks = lockfile.graph.locks
- cached_cookbooks = resolver.resolve.collect do |name, version, dependency|
- lock_deps << dependency
- dependency.locked_version ||= Solve::Version.new(version)
- if dependency.downloaded?
- Berkshelf.formatter.use(dependency.name, dependency.cached_cookbook.version, dependency.location)
- dependency.cached_cookbook
- else
- source = berksfile.sources.find { |source| source.cookbook(name, version) }
- remote_cookbook = source.cookbook(name, version)
- Berkshelf.formatter.install(name, version, api_source: source, location_type: remote_cookbook.location_type,
- location_path: remote_cookbook.location_path)
- temp_filepath = downloader.download(name, version)
- CookbookStore.import(name, version, temp_filepath)
- end
+ # Only construct the universe if we are going to download things
+ unless locks.all? { |_, dependency| dependency.downloaded? }
+ build_universe
end
- verify_licenses!(lock_deps)
- lockfile.update(lock_deps)
- cached_cookbooks
+ locks.sort.collect do |name, dependency|
+ install(dependency)
+ end
end
- # Verify that the licenses of all the cached cookbooks fall in the realm of
- # allowed licenses from the Berkshelf Config.
+ # Resolve and install the dependencies from the "universe", updating the
+ # lockfile appropiately.
#
- # @param [Array<Berkshelf::Dependencies>] dependencies
- #
- # @raise [Berkshelf::LicenseNotAllowed]
- # if the license is not permitted and `raise_license_exception` is true
- def verify_licenses!(dependencies)
- licenses = Array(Berkshelf.config.allowed_licenses)
- return if licenses.empty?
+ # @return [Array<CachedCookbook>]
+ # the list of installed cookbooks
+ def install_from_universe
+ dependencies = lockfile.graph.locks.values + berksfile.dependencies
+ dependencies = dependencies.inject({}) do |hash, dependency|
+ # Fancy way of ensuring no duplicate dependencies are used...
+ hash[dependency.name] ||= dependency
+ hash
+ end.values
- dependencies.each do |dependency|
- next if dependency.location.is_a?(Berkshelf::PathLocation)
- cached = dependency.cached_cookbook
+ resolver = Resolver.new(berksfile, dependencies)
- begin
- unless licenses.include?(cached.metadata.license)
- raise Berkshelf::LicenseNotAllowed.new(cached)
- end
- rescue Berkshelf::LicenseNotAllowed => e
- if Berkshelf.config.raise_license_exception
- FileUtils.rm_rf(cached.path)
- raise
- end
+ # Download all SCM locations first, since they might have additional
+ # constraints that we don't yet know about
+ dependencies.select(&:scm_location?).each do |dependency|
+ Berkshelf.formatter.fetch(dependency)
+ dependency.download
+ end
- Berkshelf.ui.warn(e.to_s)
+ # Unlike when installing from the lockfile, we _always_ need to build
+ # the universe when installing from the universe... duh
+ build_universe
+
+ # Add any explicit dependencies for already-downloaded cookbooks (like
+ # path locations)
+ dependencies.each do |dependency|
+ if cookbook = dependency.cached_cookbook
+ resolver.add_explicit_dependencies(cookbook)
end
end
+
+ resolver.resolve.sort.collect do |dependency|
+ install(dependency)
+ end
end
- private
+ # Install a specific dependency.
+ #
+ # @param [Dependency]
+ # the dependency to install
+ # @return [CachedCookbook]
+ # the installed cookbook
+ def install(dependency)
+ if dependency.downloaded?
+ Berkshelf.formatter.use(dependency)
+ dependency.cached_cookbook
+ else
+ name, version = dependency.name, dependency.locked_version.to_s
+ source = berksfile.source_for(name, version)
+ cookbook = source.cookbook(name, version)
- # Returns an instance of `Berkshelf::Dependency` with an equality constraint matching
- # the locked version of the dependency in the lockfile.
- #
- # If no matching dependency is found in the lockfile then nil is returned.
- #
- # @param [Berkshelf:Dependency] dependency
- #
- # @return [Berkshelf::Dependency, nil]
- def dependency_from_lockfile(dependency)
- locked = lockfile.find(dependency)
+ Berkshelf.formatter.install(source, cookbook)
- return nil unless locked
-
- # If there's a locked_version, make sure it's still satisfied
- # by the constraint
- if locked.locked_version
- unless dependency.version_constraint.satisfies?(locked.locked_version)
- raise Berkshelf::OutdatedDependency.new(locked, dependency)
- end
- end
-
- locked.version_constraint = Solve::Constraint.new("= #{locked.locked_version}")
- locked
+ stash = downloader.download(name, version)
+ CookbookStore.import(name, version, stash)
end
+ end
- # Merge the locked dependencies against the given dependencies.
- #
- # For each the given dependencies, check if there's a locked version that
- # still satisfies the version constraint. If it does, "lock" that dependency
- # because we should just use the locked version.
- #
- # If a locked dependency exists, but doesn't satisfy the constraint, raise a
- # {Berkshelf::OutdatedDependency} and tell the user to run update.
- #
- # Never use the locked constraint for a dependency with a {PathLocation}
- #
- # @param [Array<Berkshelf::Dependency>] dependencies
- #
- # @return [Array<Berkshelf::Dependency>]
- def lockfile_reduce(dependencies = [])
- {}.tap do |h|
- (dependencies + lockfile.dependencies).each do |dependency|
- next if h.has_key?(dependency.name)
+ private
- if dependency.path_location?
- result = dependency
- else
- result = dependency_from_lockfile(dependency) || dependency
- 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
+ # the lockfile from "leaking" dependencies when they have been removed
+ # from the Berksfile, but still remained locked in the lockfile.
+ #
+ # If the dependency exists, a constraint comparison is conducted to verify
+ # that the locked dependency still satisifes the original constraint. This
+ # handles the edge case where a user has updated or removed a constraint
+ # on a dependency that already existed in the lockfile.
+ #
+ # @raise [OutdatedDependency]
+ # if the constraint exists, but is no longer satisifed by the existing
+ # locked version
+ #
+ # @return [Array<Dependency>]
+ def reduce_lockfile!
+ lockfile.dependencies.each do |dependency|
+ if berksfile.dependencies.map(&:name).include?(dependency.name)
+ locked = lockfile.graph.find(dependency)
+ next if locked.nil?
- h[result.name] = result
+ unless dependency.version_constraint.satisfies?(locked.version)
+ raise OutdatedDependency.new(locked, dependency)
end
- end.values
+ else
+ lockfile.unlock(dependency)
+ end
end
- # The list of dependencies "locked" by the lockfile.
- #
- # @return [Array<Berkshelf::Dependency>]
- # the list of dependencies in this lockfile
- def locked_dependencies
- lockfile.dependencies
- end
-
- def reduce_scm_locations(dependencies)
- dependencies.select { |dependency| SCM_LOCATIONS.include?(dependency.class.location_key) }
- end
+ lockfile.save
+ end
end
end