berkshelf 3.0.0.beta6 → 3.0.0.beta7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/features/berksfile.feature +2 -0
- data/features/commands/apply.feature +1 -1
- data/features/commands/contingent.feature +5 -3
- data/features/commands/install.feature +40 -40
- data/features/commands/list.feature +42 -20
- data/features/commands/outdated.feature +60 -16
- data/features/commands/show.feature +51 -8
- data/features/commands/update.feature +43 -15
- data/features/commands/upload.feature +4 -1
- data/features/commands/vendor.feature +27 -0
- data/features/json_formatter.feature +20 -8
- data/features/lockfile.feature +192 -71
- data/generator_files/CHANGELOG.md.erb +5 -0
- data/lib/berkshelf/berksfile.rb +166 -139
- data/lib/berkshelf/cli.rb +33 -30
- data/lib/berkshelf/cookbook_generator.rb +1 -0
- data/lib/berkshelf/dependency.rb +64 -14
- data/lib/berkshelf/downloader.rb +7 -10
- data/lib/berkshelf/errors.rb +59 -11
- data/lib/berkshelf/formatters/human_readable.rb +23 -36
- data/lib/berkshelf/formatters/json.rb +25 -29
- data/lib/berkshelf/installer.rb +111 -122
- data/lib/berkshelf/locations/git_location.rb +22 -9
- data/lib/berkshelf/locations/mercurial_location.rb +20 -5
- data/lib/berkshelf/locations/path_location.rb +22 -7
- data/lib/berkshelf/lockfile.rb +435 -203
- data/lib/berkshelf/resolver.rb +4 -2
- data/lib/berkshelf/source.rb +10 -1
- data/lib/berkshelf/version.rb +1 -1
- data/spec/fixtures/cookbooks/example_cookbook/Berksfile.lock +3 -4
- data/spec/fixtures/lockfiles/2.0.lock +17 -0
- data/spec/fixtures/lockfiles/blank.lock +0 -0
- data/spec/fixtures/lockfiles/default.lock +18 -10
- data/spec/fixtures/lockfiles/empty.lock +3 -0
- data/spec/unit/berkshelf/berksfile_spec.rb +31 -74
- data/spec/unit/berkshelf/cookbook_generator_spec.rb +4 -0
- data/spec/unit/berkshelf/installer_spec.rb +4 -7
- data/spec/unit/berkshelf/lockfile_parser_spec.rb +124 -0
- data/spec/unit/berkshelf/lockfile_spec.rb +140 -163
- metadata +11 -6
- data/features/licenses.feature +0 -79
- data/features/step_definitions/lockfile_steps.rb +0 -57
@@ -39,34 +39,30 @@ module Berkshelf
|
|
39
39
|
|
40
40
|
# Add a Cookbook installation entry to delayed output
|
41
41
|
#
|
42
|
-
# @param [
|
43
|
-
#
|
44
|
-
# @
|
45
|
-
# the
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
cookbooks[cookbook][:api_source] = options[:api_source] unless options[:api_source] == Berkshelf::Berksfile::DEFAULT_API_URL
|
54
|
-
cookbooks[cookbook][:location_path] = options[:location_path] unless options[:api_source] == Berkshelf::Berksfile::DEFAULT_API_URL
|
42
|
+
# @param [Source] source
|
43
|
+
# the source the dependency is being downloaded from
|
44
|
+
# @param [RemoteCookbook] cookbook
|
45
|
+
# the cookbook to be downloaded
|
46
|
+
def install(source, cookbook)
|
47
|
+
cookbooks[cookbook.name] ||= {}
|
48
|
+
cookbooks[cookbook.name][:version] = cookbook.version
|
49
|
+
|
50
|
+
unless source.default?
|
51
|
+
cookbooks[cookbook.name][:api_source] = source.uri
|
52
|
+
cookbooks[cookbook.name][:location_path] = cookbook.location_path
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
58
56
|
# Add a Cookbook use entry to delayed output
|
59
57
|
#
|
60
|
-
# @param [
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
cookbooks[cookbook][:metadata] = true if location.metadata?
|
69
|
-
cookbooks[cookbook][:location] = location.relative_path
|
58
|
+
# @param [Dependency] dependency
|
59
|
+
def use(dependency)
|
60
|
+
cookbooks[dependency.name] ||= {}
|
61
|
+
cookbooks[dependency.name][:version] = dependency.cached_cookbook.version
|
62
|
+
|
63
|
+
if dependency.location.is_a?(PathLocation)
|
64
|
+
cookbooks[dependency.name][:metadata] = true if dependency.location.metadata?
|
65
|
+
cookbooks[dependency.name][:location] = dependency.location.relative_path
|
70
66
|
end
|
71
67
|
end
|
72
68
|
|
@@ -111,13 +107,13 @@ module Berkshelf
|
|
111
107
|
|
112
108
|
# Output a list of cookbooks to delayed output
|
113
109
|
#
|
114
|
-
# @param [
|
115
|
-
def list(
|
116
|
-
|
117
|
-
cookbooks[
|
118
|
-
cookbooks[
|
110
|
+
# @param [Array<Dependency>] dependencies
|
111
|
+
def list(dependencies)
|
112
|
+
dependencies.each do |dependency, cookbook|
|
113
|
+
cookbooks[dependency.name] ||= {}
|
114
|
+
cookbooks[dependency.name][:version] = dependency.locked_version.to_s
|
119
115
|
if dependency.location
|
120
|
-
cookbooks[
|
116
|
+
cookbooks[dependency.name][:location] = dependency.location
|
121
117
|
end
|
122
118
|
end
|
123
119
|
end
|
data/lib/berkshelf/installer.rb
CHANGED
@@ -2,16 +2,14 @@ require 'berkshelf/api-client'
|
|
2
2
|
|
3
3
|
module Berkshelf
|
4
4
|
class Installer
|
5
|
-
extend Forwardable
|
6
|
-
|
7
5
|
attr_reader :berksfile
|
6
|
+
attr_reader :lockfile
|
8
7
|
attr_reader :downloader
|
9
8
|
|
10
|
-
def_delegator :berksfile, :lockfile
|
11
|
-
|
12
9
|
# @param [Berkshelf::Berksfile] berksfile
|
13
10
|
def initialize(berksfile)
|
14
11
|
@berksfile = berksfile
|
12
|
+
@lockfile = berksfile.lockfile
|
15
13
|
@downloader = Downloader.new(berksfile)
|
16
14
|
end
|
17
15
|
|
@@ -19,6 +17,7 @@ module Berkshelf
|
|
19
17
|
berksfile.sources.collect do |source|
|
20
18
|
Thread.new do
|
21
19
|
begin
|
20
|
+
Berkshelf.formatter.msg("Fetching cookbook index from #{source.uri}...")
|
22
21
|
source.build_universe
|
23
22
|
rescue Berkshelf::APIClientError => ex
|
24
23
|
Berkshelf.formatter.warn "Error retrieving universe from source: #{source}"
|
@@ -28,146 +27,136 @@ module Berkshelf
|
|
28
27
|
end.map(&:join)
|
29
28
|
end
|
30
29
|
|
31
|
-
# @option options [Array<String>, String] cookbooks
|
32
|
-
#
|
33
30
|
# @return [Array<Berkshelf::CachedCookbook>]
|
34
|
-
def run
|
35
|
-
|
36
|
-
resolver = Resolver.new(berksfile, dependencies)
|
37
|
-
lock_deps = []
|
38
|
-
|
39
|
-
dependencies.each do |dependency|
|
40
|
-
if dependency.scm_location?
|
41
|
-
Berkshelf.formatter.fetch(dependency)
|
42
|
-
downloader.download(dependency)
|
43
|
-
end
|
44
|
-
|
45
|
-
next if (cookbook = dependency.cached_cookbook).nil?
|
31
|
+
def run
|
32
|
+
reduce_lockfile!
|
46
33
|
|
47
|
-
|
34
|
+
cookbooks = if lockfile.trusted?
|
35
|
+
install_from_lockfile
|
36
|
+
else
|
37
|
+
install_from_universe
|
48
38
|
end
|
49
39
|
|
50
|
-
|
51
|
-
|
40
|
+
lockfile.graph.update(cookbooks)
|
41
|
+
lockfile.update(berksfile.dependencies)
|
42
|
+
lockfile.save
|
52
43
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
44
|
+
cookbooks
|
45
|
+
end
|
46
|
+
|
47
|
+
# Install all the dependencies from the lockfile graph.
|
48
|
+
#
|
49
|
+
# @return [Array<CachedCookbook>]
|
50
|
+
# the list of installed cookbooks
|
51
|
+
def install_from_lockfile
|
52
|
+
locks = lockfile.graph.locks
|
53
|
+
|
54
|
+
# Only construct the universe if we are going to download things
|
55
|
+
unless locks.all? { |_, dependency| dependency.downloaded? }
|
56
|
+
build_universe
|
67
57
|
end
|
68
58
|
|
69
|
-
|
70
|
-
|
71
|
-
|
59
|
+
locks.sort.collect do |name, dependency|
|
60
|
+
install(dependency)
|
61
|
+
end
|
72
62
|
end
|
73
63
|
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# @param [Array<Berkshelf::Dependencies>] dependencies
|
64
|
+
# Resolve and install the dependencies from the "universe", updating the
|
65
|
+
# lockfile appropiately.
|
78
66
|
#
|
79
|
-
# @
|
80
|
-
#
|
81
|
-
def
|
82
|
-
|
83
|
-
|
67
|
+
# @return [Array<CachedCookbook>]
|
68
|
+
# the list of installed cookbooks
|
69
|
+
def install_from_universe
|
70
|
+
dependencies = lockfile.graph.locks.values + berksfile.dependencies
|
71
|
+
dependencies = dependencies.inject({}) do |hash, dependency|
|
72
|
+
# Fancy way of ensuring no duplicate dependencies are used...
|
73
|
+
hash[dependency.name] ||= dependency
|
74
|
+
hash
|
75
|
+
end.values
|
76
|
+
|
77
|
+
resolver = Resolver.new(berksfile, dependencies)
|
78
|
+
|
79
|
+
# Download all SCM locations first, since they might have additional
|
80
|
+
# constraints that we don't yet know about
|
81
|
+
dependencies.select(&:scm_location?).each do |dependency|
|
82
|
+
Berkshelf.formatter.fetch(dependency)
|
83
|
+
dependency.download
|
84
|
+
end
|
85
|
+
|
86
|
+
# Unlike when installing from the lockfile, we _always_ need to build
|
87
|
+
# the universe when installing from the universe... duh
|
88
|
+
build_universe
|
84
89
|
|
90
|
+
# Add any explicit dependencies for already-downloaded cookbooks (like
|
91
|
+
# path locations)
|
85
92
|
dependencies.each do |dependency|
|
86
|
-
|
87
|
-
|
93
|
+
if cookbook = dependency.cached_cookbook
|
94
|
+
resolver.add_explicit_dependencies(cookbook)
|
95
|
+
end
|
96
|
+
end
|
88
97
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
rescue Berkshelf::LicenseNotAllowed => e
|
94
|
-
if Berkshelf.config.raise_license_exception
|
95
|
-
FileUtils.rm_rf(cached.path)
|
96
|
-
raise
|
97
|
-
end
|
98
|
+
resolver.resolve.sort.collect do |dependency|
|
99
|
+
install(dependency)
|
100
|
+
end
|
101
|
+
end
|
98
102
|
|
99
|
-
|
100
|
-
|
103
|
+
# Install a specific dependency.
|
104
|
+
#
|
105
|
+
# @param [Dependency]
|
106
|
+
# the dependency to install
|
107
|
+
# @return [CachedCookbook]
|
108
|
+
# the installed cookbook
|
109
|
+
def install(dependency)
|
110
|
+
if dependency.downloaded?
|
111
|
+
Berkshelf.formatter.use(dependency)
|
112
|
+
dependency.cached_cookbook
|
113
|
+
else
|
114
|
+
name, version = dependency.name, dependency.locked_version.to_s
|
115
|
+
source = berksfile.source_for(name, version)
|
116
|
+
cookbook = source.cookbook(name, version)
|
117
|
+
|
118
|
+
Berkshelf.formatter.install(source, cookbook)
|
119
|
+
|
120
|
+
stash = downloader.download(name, version)
|
121
|
+
CookbookStore.import(name, version, stash)
|
101
122
|
end
|
102
123
|
end
|
103
124
|
|
104
125
|
private
|
105
126
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
127
|
+
# Iterate over each top-level dependency defined in the lockfile and
|
128
|
+
# check if that dependency is still defined in the Berksfile.
|
129
|
+
#
|
130
|
+
# If the dependency is no longer present in the Berksfile, it is "safely"
|
131
|
+
# removed using {Lockfile#unlock} and {Lockfile#remove}. This prevents
|
132
|
+
# the lockfile from "leaking" dependencies when they have been removed
|
133
|
+
# from the Berksfile, but still remained locked in the lockfile.
|
134
|
+
#
|
135
|
+
# If the dependency exists, a constraint comparison is conducted to verify
|
136
|
+
# that the locked dependency still satisifes the original constraint. This
|
137
|
+
# handles the edge case where a user has updated or removed a constraint
|
138
|
+
# on a dependency that already existed in the lockfile.
|
139
|
+
#
|
140
|
+
# @raise [OutdatedDependency]
|
141
|
+
# if the constraint exists, but is no longer satisifed by the existing
|
142
|
+
# locked version
|
143
|
+
#
|
144
|
+
# @return [Array<Dependency>]
|
145
|
+
def reduce_lockfile!
|
146
|
+
lockfile.dependencies.each do |dependency|
|
147
|
+
if berksfile.dependencies.map(&:name).include?(dependency.name)
|
148
|
+
locked = lockfile.graph.find(dependency)
|
149
|
+
next if locked.nil?
|
150
|
+
|
151
|
+
unless dependency.version_constraint.satisfies?(locked.version)
|
152
|
+
raise OutdatedDependency.new(locked, dependency)
|
124
153
|
end
|
154
|
+
else
|
155
|
+
lockfile.unlock(dependency)
|
125
156
|
end
|
126
|
-
|
127
|
-
locked.version_constraint = Solve::Constraint.new("= #{locked.locked_version}")
|
128
|
-
locked
|
129
|
-
end
|
130
|
-
|
131
|
-
# Merge the locked dependencies against the given dependencies.
|
132
|
-
#
|
133
|
-
# For each the given dependencies, check if there's a locked version that
|
134
|
-
# still satisfies the version constraint. If it does, "lock" that dependency
|
135
|
-
# because we should just use the locked version.
|
136
|
-
#
|
137
|
-
# If a locked dependency exists, but doesn't satisfy the constraint, raise a
|
138
|
-
# {Berkshelf::OutdatedDependency} and tell the user to run update.
|
139
|
-
#
|
140
|
-
# Never use the locked constraint for a dependency with a {PathLocation}
|
141
|
-
#
|
142
|
-
# @param [Array<Berkshelf::Dependency>] dependencies
|
143
|
-
#
|
144
|
-
# @return [Array<Berkshelf::Dependency>]
|
145
|
-
def lockfile_reduce(dependencies = [])
|
146
|
-
{}.tap do |h|
|
147
|
-
(dependencies + lockfile.dependencies).each do |dependency|
|
148
|
-
next if h.has_key?(dependency.name)
|
149
|
-
|
150
|
-
if dependency.path_location?
|
151
|
-
result = dependency
|
152
|
-
else
|
153
|
-
result = dependency_from_lockfile(dependency) || dependency
|
154
|
-
end
|
155
|
-
|
156
|
-
h[result.name] = result
|
157
|
-
end
|
158
|
-
end.values
|
159
157
|
end
|
160
158
|
|
161
|
-
|
162
|
-
|
163
|
-
# @return [Array<Berkshelf::Dependency>]
|
164
|
-
# the list of dependencies in this lockfile
|
165
|
-
def locked_dependencies
|
166
|
-
lockfile.dependencies
|
167
|
-
end
|
168
|
-
|
169
|
-
def reduce_scm_locations(dependencies)
|
170
|
-
dependencies.select { |dependency| SCM_LOCATIONS.include?(dependency.class.location_key) }
|
171
|
-
end
|
159
|
+
lockfile.save
|
160
|
+
end
|
172
161
|
end
|
173
162
|
end
|
@@ -68,7 +68,7 @@ module Berkshelf
|
|
68
68
|
|
69
69
|
tmp_path = rel ? File.join(repo_path, rel) : repo_path
|
70
70
|
unless File.chef_cookbook?(tmp_path)
|
71
|
-
msg = "Cookbook '#{dependency.name}' not found at
|
71
|
+
msg = "Cookbook '#{dependency.name}' not found at #{to_s}"
|
72
72
|
msg << " at path '#{rel}'" if rel
|
73
73
|
raise CookbookNotFound, msg
|
74
74
|
end
|
@@ -90,18 +90,31 @@ module Berkshelf
|
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
93
|
+
def ==(other)
|
94
|
+
other.is_a?(GitLocation) &&
|
95
|
+
other.uri == uri &&
|
96
|
+
other.branch == branch &&
|
97
|
+
other.ref == ref &&
|
98
|
+
other.rel == rel
|
99
|
+
end
|
100
|
+
|
93
101
|
def to_s
|
94
|
-
|
102
|
+
if rel
|
103
|
+
"#{uri} (at #{branch || ref[0...7]}/#{rel})"
|
104
|
+
else
|
105
|
+
"#{uri} (at #{branch || ref[0...7]})"
|
106
|
+
end
|
95
107
|
end
|
96
108
|
|
97
|
-
|
109
|
+
def to_lock
|
110
|
+
out = " git: #{uri}\n"
|
111
|
+
out << " branch: #{branch}\n" if branch
|
112
|
+
out << " ref: #{ref}\n" if ref
|
113
|
+
out << " rel: #{rel}\n" if rel
|
114
|
+
out
|
115
|
+
end
|
98
116
|
|
99
|
-
|
100
|
-
info = checkout_info
|
101
|
-
s = "'#{uri}' with #{info[:kind]}: '#{info[:rev]}'"
|
102
|
-
s << " at ref: '#{ref}'" if ref && (info[:kind] != "ref" || ref != info[:rev])
|
103
|
-
s
|
104
|
-
end
|
117
|
+
private
|
105
118
|
|
106
119
|
def cached?(destination)
|
107
120
|
revision_path(destination) && File.exists?(revision_path(destination))
|
@@ -49,8 +49,7 @@ module Berkshelf
|
|
49
49
|
|
50
50
|
tmp_path = rel ? File.join(repo_path, rel) : repo_path
|
51
51
|
unless File.chef_cookbook?(tmp_path)
|
52
|
-
msg = "Cookbook '#{dependency.name}' not found at
|
53
|
-
msg << " with rev '#{rev}'" if rev
|
52
|
+
msg = "Cookbook '#{dependency.name}' not found at #{to_s}"
|
54
53
|
msg << " at path '#{rel}'" if rel
|
55
54
|
raise CookbookNotFound, msg
|
56
55
|
end
|
@@ -72,10 +71,26 @@ module Berkshelf
|
|
72
71
|
end
|
73
72
|
end
|
74
73
|
|
74
|
+
def ==(other)
|
75
|
+
other.is_a?(MercurialLocation) &&
|
76
|
+
other.uri == uri &&
|
77
|
+
other.rev == rev &&
|
78
|
+
other.rel == rel
|
79
|
+
end
|
80
|
+
|
75
81
|
def to_s
|
76
|
-
|
77
|
-
|
78
|
-
|
82
|
+
if rel
|
83
|
+
"#{uri} (at #{rev}/#{rel})"
|
84
|
+
else
|
85
|
+
"#{uri} (at #{rev})"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_lock
|
90
|
+
out = " hg: #{uri}\n"
|
91
|
+
out << " rev: #{rev}\n" if rev
|
92
|
+
out << " rel: #{rel}\n" if rel
|
93
|
+
out
|
79
94
|
end
|
80
95
|
|
81
96
|
private
|
@@ -49,6 +49,15 @@ module Berkshelf
|
|
49
49
|
"./#{new_path}"
|
50
50
|
end
|
51
51
|
|
52
|
+
#
|
53
|
+
# The expanded path of this path on disk, relative to the berksfile.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
#
|
57
|
+
def expanded_path
|
58
|
+
relative_path(dependency.berksfile.filepath)
|
59
|
+
end
|
60
|
+
|
52
61
|
# Valid if the path exists and is readable
|
53
62
|
#
|
54
63
|
# @return [Boolean]
|
@@ -60,14 +69,20 @@ module Berkshelf
|
|
60
69
|
super.merge(value: self.path)
|
61
70
|
end
|
62
71
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
72
|
+
def ==(other)
|
73
|
+
other.is_a?(PathLocation) &&
|
74
|
+
other.metadata? == metadata? &&
|
75
|
+
other.expanded_path == expanded_path
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_lock
|
79
|
+
out = " path: #{relative_path(dependency.berksfile.filepath)}\n"
|
80
|
+
out << " metadata: true\n" if metadata?
|
81
|
+
out
|
82
|
+
end
|
83
|
+
|
69
84
|
def to_s
|
70
|
-
"#{
|
85
|
+
"source at #{relative_path(dependency.berksfile.filepath)}"
|
71
86
|
end
|
72
87
|
end
|
73
88
|
end
|