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
data/lib/berkshelf/lockfile.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
require_relative 'dependency'
|
2
2
|
|
3
3
|
module Berkshelf
|
4
|
-
# The object representation of the Berkshelf lockfile. The lockfile is useful
|
5
|
-
# when working in teams where the same cookbook versions are desired across
|
6
|
-
# multiple workstations.
|
7
4
|
class Lockfile
|
8
5
|
class << self
|
9
6
|
# Initialize a Lockfile from the given filepath
|
@@ -24,7 +21,10 @@ module Berkshelf
|
|
24
21
|
end
|
25
22
|
end
|
26
23
|
|
27
|
-
DEFAULT_FILENAME =
|
24
|
+
DEFAULT_FILENAME = 'Berksfile.lock'
|
25
|
+
|
26
|
+
DEPENDENCIES = 'DEPENDENCIES'
|
27
|
+
GRAPH = 'GRAPH'
|
28
28
|
|
29
29
|
include Berkshelf::Mixin::Logging
|
30
30
|
|
@@ -36,6 +36,10 @@ module Berkshelf
|
|
36
36
|
# the Berksfile for this Lockfile
|
37
37
|
attr_reader :berksfile
|
38
38
|
|
39
|
+
# @return [Hash]
|
40
|
+
# the dependency graph
|
41
|
+
attr_reader :graph
|
42
|
+
|
39
43
|
# Create a new lockfile instance associated with the given Berksfile. If a
|
40
44
|
# Lockfile exists, it is automatically loaded. Otherwise, an empty instance is
|
41
45
|
# created and ready for use.
|
@@ -48,69 +52,84 @@ module Berkshelf
|
|
48
52
|
@filepath = options[:filepath].to_s
|
49
53
|
@berksfile = options[:berksfile]
|
50
54
|
@dependencies = {}
|
55
|
+
@graph = Graph.new(self)
|
56
|
+
|
57
|
+
parse if File.exists?(@filepath)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parse the lockfile.
|
61
|
+
#
|
62
|
+
# @return true
|
63
|
+
def parse
|
64
|
+
LockfileParser.new(self).run
|
65
|
+
true
|
66
|
+
rescue => e
|
67
|
+
raise LockfileParserError.new(e)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Determine if this lockfile actually exists on disk.
|
71
|
+
#
|
72
|
+
# @return [Boolean]
|
73
|
+
# true if this lockfile exists on the disk, false otherwise
|
74
|
+
def present?
|
75
|
+
File.exists?(filepath) && !File.read(filepath).strip.empty?
|
76
|
+
end
|
51
77
|
|
52
|
-
|
78
|
+
# Determine if we can "trust" this lockfile. A lockfile is trustworthy if:
|
79
|
+
#
|
80
|
+
# 1. All dependencies defined in the Berksfile are present in this
|
81
|
+
# lockfile
|
82
|
+
# 2. Each dependency's constraint in the Berksfile is still satisifed by
|
83
|
+
# the currently locked version
|
84
|
+
#
|
85
|
+
# This method does _not_ account for leaky dependencies (i.e. dependencies
|
86
|
+
# defined in the lockfile that are no longer present in the Berksfile); this
|
87
|
+
# edge case is handed by the installer.
|
88
|
+
#
|
89
|
+
# @return [Boolean]
|
90
|
+
# true if this lockfile is trusted, false otherwise
|
91
|
+
def trusted?
|
92
|
+
berksfile.dependencies.all? do |dependency|
|
93
|
+
locked = find(dependency)
|
94
|
+
graphed = graph.find(dependency)
|
95
|
+
constraint = dependency.version_constraint
|
96
|
+
|
97
|
+
locked && graphed &&
|
98
|
+
dependency.location == locked.location &&
|
99
|
+
constraint.satisfies?(graphed.version)
|
100
|
+
end
|
53
101
|
end
|
54
102
|
|
55
|
-
# Resolve this Berksfile and apply the locks found in the generated
|
56
|
-
# target Chef environment
|
103
|
+
# Resolve this Berksfile and apply the locks found in the generated
|
104
|
+
# +Berksfile.lock+ to the target Chef environment
|
57
105
|
#
|
58
|
-
# @param [String]
|
106
|
+
# @param [String] name
|
107
|
+
# the name of the environment to apply the locks to
|
59
108
|
#
|
60
109
|
# @option options [Hash] :ssl_verify (true)
|
61
110
|
# Disable/Enable SSL verification during uploads
|
62
111
|
#
|
63
112
|
# @raise [EnvironmentNotFound]
|
64
|
-
# if the target environment was not found
|
113
|
+
# if the target environment was not found on the remote Chef Server
|
65
114
|
# @raise [ChefConnectionError]
|
66
|
-
# if you are locking cookbooks with an invalid or not-specified client
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
115
|
+
# if you are locking cookbooks with an invalid or not-specified client
|
116
|
+
# configuration
|
117
|
+
def apply(name, options = {})
|
118
|
+
Berkshelf.ridley_connection(options) do |connection|
|
119
|
+
environment = connection.environment.find(name)
|
72
120
|
|
73
|
-
|
74
|
-
dependencies.each do |dependency|
|
75
|
-
if dependency.locked_version.nil?
|
76
|
-
# A locked version must be present for each entry. Older versions of the lockfile
|
77
|
-
# may have contained dependencies with a special type of location that would attempt
|
78
|
-
# to dynamically determine the locked version. This is incorrect and the Lockfile
|
79
|
-
# should be regenerated if that is the case.
|
80
|
-
raise InvalidLockFile, "Your lockfile contains a dependency without a locked version. This " +
|
81
|
-
"may be because you have an old lockfile. Regenerate your lockfile and try again."
|
82
|
-
end
|
121
|
+
raise EnvironmentNotFound.new(name) if environment.nil?
|
83
122
|
|
84
|
-
|
85
|
-
|
123
|
+
locks = graph.locks.inject({}) do |hash, (name, dependency)|
|
124
|
+
hash[name] = "= #{dependency.locked_version.to_s}"
|
125
|
+
hash
|
86
126
|
end
|
87
127
|
|
128
|
+
environment.cookbook_versions = locks
|
88
129
|
environment.save
|
89
130
|
end
|
90
131
|
end
|
91
132
|
|
92
|
-
# Load the lockfile from file system.
|
93
|
-
def load!
|
94
|
-
contents = File.read(filepath).strip
|
95
|
-
hash = parse(contents)
|
96
|
-
|
97
|
-
hash[:dependencies].each do |name, options|
|
98
|
-
# Dynamically calculate paths relative to the Berksfile if a path is given
|
99
|
-
options[:path] &&= File.expand_path(options[:path], File.dirname(filepath))
|
100
|
-
|
101
|
-
begin
|
102
|
-
dependency = Berkshelf::Dependency.new(berksfile, name.to_s, options)
|
103
|
-
next if dependency.location && !dependency.location.valid?
|
104
|
-
add(dependency)
|
105
|
-
rescue Berkshelf::CookbookNotFound
|
106
|
-
# It's possible that a source is locked that contains a path location, and
|
107
|
-
# that path location was renamed or no longer exists. When loading the
|
108
|
-
# lockfile, Berkshelf will throw an error if it can't find a cookbook that
|
109
|
-
# previously existed at a path location.
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
133
|
# The list of dependencies constrained in this lockfile.
|
115
134
|
#
|
116
135
|
# @return [Array<Berkshelf::Dependency>]
|
@@ -129,7 +148,7 @@ module Berkshelf
|
|
129
148
|
# @return [Berkshelf::Dependency, nil]
|
130
149
|
# the cookbook dependency from this lockfile or nil if one was not found
|
131
150
|
def find(dependency)
|
132
|
-
@dependencies[
|
151
|
+
@dependencies[Dependency.name(dependency)]
|
133
152
|
end
|
134
153
|
|
135
154
|
# Determine if this lockfile contains the given dependency.
|
@@ -139,222 +158,435 @@ module Berkshelf
|
|
139
158
|
#
|
140
159
|
# @return [Boolean]
|
141
160
|
# true if the dependency exists, false otherwise
|
142
|
-
def
|
161
|
+
def dependency?(dependency)
|
143
162
|
!find(dependency).nil?
|
144
163
|
end
|
164
|
+
alias_method :has_dependency?, :dependency?
|
145
165
|
|
146
|
-
#
|
147
|
-
#
|
166
|
+
# Add a new cookbok to the lockfile. If an entry already exists by the
|
167
|
+
# given name, it will be overwritten.
|
148
168
|
#
|
149
|
-
# @param [
|
150
|
-
# the
|
151
|
-
|
152
|
-
|
169
|
+
# @param [Dependency] dependency
|
170
|
+
# the dependency to add
|
171
|
+
#
|
172
|
+
# @return [Dependency]
|
173
|
+
def add(dependency)
|
174
|
+
@dependencies[Dependency.name(dependency)] = dependency
|
175
|
+
end
|
176
|
+
|
177
|
+
# Retrieve information about a given cookbook that is in this lockfile.
|
178
|
+
#
|
179
|
+
# @raise [DependencyNotFound]
|
180
|
+
# if this lockfile does not have the given dependency
|
181
|
+
# @raise [CookbookNotFound]
|
182
|
+
# if this lockfile has the dependency, but the cookbook is not downloaded
|
183
|
+
#
|
184
|
+
# @param [String, Dependency] dependency
|
185
|
+
# the dependency or name of the dependency to find
|
186
|
+
#
|
187
|
+
# @return [CachedCookbook]
|
188
|
+
# the CachedCookbook that corresponds to the given name parameter
|
189
|
+
def retrieve(dependency)
|
190
|
+
locked = graph.locks[Dependency.name(dependency)]
|
153
191
|
|
154
|
-
|
155
|
-
|
192
|
+
if locked.nil?
|
193
|
+
raise DependencyNotFound.new(Dependency.name(dependency))
|
194
|
+
end
|
195
|
+
|
196
|
+
unless locked.downloaded?
|
197
|
+
raise CookbookNotFound, "Could not find cookbook '#{locked.to_s}'. " \
|
198
|
+
"Run `berks install` to download and install the missing cookbook."
|
199
|
+
end
|
200
|
+
|
201
|
+
locked.cached_cookbook
|
156
202
|
end
|
157
203
|
|
158
|
-
#
|
204
|
+
# Replace the list of dependencies.
|
159
205
|
#
|
160
|
-
# @param [Berkshelf::Dependency]
|
161
|
-
# the
|
162
|
-
def
|
163
|
-
@dependencies
|
206
|
+
# @param [Array<Berkshelf::Dependency>] dependencies
|
207
|
+
# the list of dependencies to update
|
208
|
+
def update(dependencies)
|
209
|
+
@dependencies = {}
|
210
|
+
|
211
|
+
dependencies.each do |dependency|
|
212
|
+
@dependencies[Dependency.name(dependency)] = dependency
|
213
|
+
end
|
164
214
|
end
|
165
|
-
alias_method :append, :add
|
166
215
|
|
167
|
-
# Remove the given dependency from this lockfile. This method accepts a
|
168
|
-
# attribute which may either be the name of a cookbook
|
169
|
-
# actual
|
216
|
+
# Remove the given dependency from this lockfile. This method accepts a
|
217
|
+
# +dependency+ attribute which may either be the name of a cookbook, as a
|
218
|
+
# String or an actual {Dependency} object.
|
170
219
|
#
|
171
|
-
#
|
172
|
-
#
|
220
|
+
# This method first removes the dependency from the list of top-level
|
221
|
+
# dependencies. Then it uses a recursive algorithm to safely remove any
|
222
|
+
# other dependencies from the graph that are no longer needed.
|
173
223
|
#
|
174
224
|
# @raise [Berkshelf::CookbookNotFound]
|
175
225
|
# if the provided dependency does not exist
|
176
|
-
|
177
|
-
|
178
|
-
|
226
|
+
#
|
227
|
+
# @param [String] dependency
|
228
|
+
# the name of the cookbook to remove
|
229
|
+
def unlock(dependency)
|
230
|
+
unless dependency?(dependency)
|
231
|
+
raise Berkshelf::CookbookNotFound, "'#{dependency}' does not exist in this lockfile!"
|
232
|
+
end
|
233
|
+
|
234
|
+
@dependencies.delete(Dependency.name(dependency))
|
235
|
+
graph.remove(dependency)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Write the contents of the current statue of the lockfile to disk. This
|
239
|
+
# method uses an atomic file write. A temporary file is created, written,
|
240
|
+
# and then copied over the existing one. This ensures any partial updates
|
241
|
+
# or failures do no affect the lockfile. The temporary file is ensured
|
242
|
+
# deletion.
|
243
|
+
#
|
244
|
+
# @return [true, false]
|
245
|
+
# true if the lockfile was saved, false otherwise
|
246
|
+
def save
|
247
|
+
return false if dependencies.empty?
|
248
|
+
|
249
|
+
tempfile = Tempfile.new(['Berksfile', '.lock'])
|
250
|
+
|
251
|
+
tempfile.write(DEPENDENCIES)
|
252
|
+
tempfile.write("\n")
|
253
|
+
dependencies.sort.each do |dependency|
|
254
|
+
tempfile.write(dependency.to_lock)
|
179
255
|
end
|
180
256
|
|
181
|
-
|
257
|
+
tempfile.write("\n")
|
258
|
+
tempfile.write(graph.to_lock)
|
259
|
+
|
260
|
+
tempfile.rewind
|
261
|
+
tempfile.close
|
262
|
+
|
263
|
+
# Move the lockfile into place
|
264
|
+
FileUtils.cp(tempfile.path, filepath)
|
265
|
+
|
266
|
+
true
|
267
|
+
ensure
|
268
|
+
tempfile.unlink if tempfile
|
182
269
|
end
|
183
|
-
alias_method :unlock, :remove
|
184
270
|
|
185
|
-
# @
|
186
|
-
# the string representation of the lockfile
|
271
|
+
# @private
|
187
272
|
def to_s
|
188
273
|
"#<Berkshelf::Lockfile #{Pathname.new(filepath).basename}>"
|
189
274
|
end
|
190
275
|
|
191
|
-
# @
|
192
|
-
# the detailed string representation of the lockfile
|
276
|
+
# @private
|
193
277
|
def inspect
|
194
278
|
"#<Berkshelf::Lockfile #{Pathname.new(filepath).basename}, dependencies: #{dependencies.inspect}>"
|
195
279
|
end
|
196
280
|
|
197
|
-
|
198
|
-
#
|
199
|
-
# @return [Hash]
|
200
|
-
# the hash representation of this lockfile
|
201
|
-
# * :dependencies [Array<Berkshelf::Dependency>] the list of dependencies
|
202
|
-
def to_hash
|
203
|
-
{
|
204
|
-
dependencies: @dependencies
|
205
|
-
}
|
206
|
-
end
|
281
|
+
private
|
207
282
|
|
208
|
-
# The
|
209
|
-
#
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
JSON.pretty_generate(to_hash, options)
|
216
|
-
end
|
283
|
+
# The class responsible for parsing the lockfile and turning it into a
|
284
|
+
# useful data structure.
|
285
|
+
class LockfileParser
|
286
|
+
NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'
|
287
|
+
DEPENDENCY_PATTERN = /^ {2}#{NAME_VERSION}$/
|
288
|
+
DEPENDENCIES_PATTERN = /^ {4}#{NAME_VERSION}$/
|
289
|
+
OPTION_PATTERN = /^ {4}(.+)\: (.+)/
|
217
290
|
|
218
|
-
|
291
|
+
# Create a new lockfile parser.
|
292
|
+
#
|
293
|
+
# @param [Lockfile]
|
294
|
+
def initialize(lockfile)
|
295
|
+
@lockfile = lockfile
|
296
|
+
@berksfile = lockfile.berksfile
|
297
|
+
end
|
219
298
|
|
220
|
-
# Parse the
|
299
|
+
# Parse the lockfile contents, adding the correct things to the lockfile.
|
221
300
|
#
|
222
|
-
# @
|
301
|
+
# @return [true]
|
302
|
+
def run
|
303
|
+
@parsed_dependencies = {}
|
304
|
+
|
305
|
+
contents = File.read(@lockfile.filepath)
|
306
|
+
|
307
|
+
if contents.strip.empty?
|
308
|
+
Berkshelf.formatter.warn "Your lockfile at '#{@lockfile.filepath}' " \
|
309
|
+
"is empty. I am going to parse it anyway, but there is a chance " \
|
310
|
+
"that a larger problem is at play. If you manually edited your " \
|
311
|
+
"lockfile, you may have corrupted it."
|
312
|
+
end
|
313
|
+
|
314
|
+
if contents.strip[0] == '{'
|
315
|
+
Berkshelf.formatter.warn "It looks like you are using an older " \
|
316
|
+
"version of the lockfile. Attempting to convert..."
|
317
|
+
|
318
|
+
dependencies = "#{Lockfile::DEPENDENCIES}\n"
|
319
|
+
graph = "#{Lockfile::GRAPH}\n"
|
320
|
+
|
321
|
+
begin
|
322
|
+
hash = JSON.parse(contents)
|
323
|
+
rescue JSON::ParserError
|
324
|
+
Berkshelf.formatter.warn "Could not convert lockfile! This is a " \
|
325
|
+
"problem. You see, previous versions of the lockfile were " \
|
326
|
+
"actually a lie. It lied to you about your version locks, and we " \
|
327
|
+
"are really sorry about that.\n\n" \
|
328
|
+
"Here's the good news - we fixed it!\n\n" \
|
329
|
+
"Here's the bad news - you probably should not trust your old " \
|
330
|
+
"lockfile. You should manually delete your old lockfile and " \
|
331
|
+
"re-run the installer."
|
332
|
+
end
|
333
|
+
|
334
|
+
hash['dependencies'] && hash['dependencies'].sort .each do |name, info|
|
335
|
+
dependencies << " #{name} (>= 0.0.0)\n"
|
336
|
+
info.each do |key, value|
|
337
|
+
unless key == 'locked_version'
|
338
|
+
dependencies << " #{key}: #{value}\n"
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
graph << " #{name} (#{info['locked_version']})\n"
|
343
|
+
end
|
344
|
+
|
345
|
+
contents = "#{dependencies}\n#{graph}"
|
346
|
+
end
|
347
|
+
|
348
|
+
contents.split(/(?:\r?\n)+/).each do |line|
|
349
|
+
if line == Lockfile::DEPENDENCIES
|
350
|
+
@state = :dependency
|
351
|
+
elsif line == Lockfile::GRAPH
|
352
|
+
@state = :graph
|
353
|
+
else
|
354
|
+
send("parse_#{@state}", line)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
@parsed_dependencies.each do |name, options|
|
359
|
+
dependency = Dependency.new(@berksfile, name, options)
|
360
|
+
@lockfile.add(dependency)
|
361
|
+
end
|
362
|
+
|
363
|
+
true
|
364
|
+
end
|
365
|
+
|
366
|
+
private
|
367
|
+
|
368
|
+
# Parse a dependency line.
|
223
369
|
#
|
224
|
-
# @
|
225
|
-
def
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
hash[:dependencies] = hash.delete(:sources)
|
370
|
+
# @param [String] line
|
371
|
+
def parse_dependency(line)
|
372
|
+
if line =~ DEPENDENCY_PATTERN
|
373
|
+
name, version = $1, $2
|
374
|
+
|
375
|
+
@parsed_dependencies[name] ||= {}
|
376
|
+
@parsed_dependencies[name][:constraint] = version if version
|
377
|
+
@current_dependency = @parsed_dependencies[name]
|
378
|
+
elsif line =~ OPTION_PATTERN
|
379
|
+
key, value = $1, $2
|
380
|
+
@current_dependency[key.to_sym] = value
|
236
381
|
end
|
382
|
+
end
|
237
383
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
if
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
384
|
+
# Parse a graph line.
|
385
|
+
#
|
386
|
+
# @param [String] line
|
387
|
+
def parse_graph(line)
|
388
|
+
if line =~ DEPENDENCY_PATTERN
|
389
|
+
name, version = $1, $2
|
390
|
+
|
391
|
+
@lockfile.graph.find(name) || @lockfile.graph.add(name, version)
|
392
|
+
@current_lock = name
|
393
|
+
elsif line =~ DEPENDENCIES_PATTERN
|
394
|
+
name, constraint = $1, $2
|
395
|
+
@lockfile.graph.find(@current_lock).add_dependency(name, constraint)
|
247
396
|
end
|
248
397
|
end
|
398
|
+
end
|
249
399
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
400
|
+
# The class representing an internal graph.
|
401
|
+
class Graph
|
402
|
+
# Create a new Lockfile graph.
|
403
|
+
#
|
404
|
+
# Some clarifying terminology:
|
405
|
+
#
|
406
|
+
# yum-epel (0.2.0) <- lock
|
407
|
+
# yum (~> 3.0) <- dependency
|
408
|
+
#
|
409
|
+
# @return [Graph]
|
410
|
+
def initialize(lockfile)
|
411
|
+
@lockfile = lockfile
|
412
|
+
@berksfile = lockfile.berksfile
|
413
|
+
@graph = {}
|
414
|
+
end
|
415
|
+
|
416
|
+
# The list of locks for this graph. Dependencies are retrieved from the
|
417
|
+
# lockfile, then the Berksfile, and finally a new dependency object is
|
418
|
+
# created if none of those exist.
|
419
|
+
#
|
420
|
+
# @return [Hash<String, Dependency>]
|
421
|
+
# a key-value hash where the key is the name of the cookbook and the
|
422
|
+
# value is the locked dependency
|
423
|
+
def locks
|
424
|
+
@graph.sort.inject({}) do |hash, (name, item)|
|
425
|
+
dependency = @lockfile.find(name) ||
|
426
|
+
@berksfile && @berksfile.find(name) ||
|
427
|
+
Dependency.new(@berksfile, name)
|
428
|
+
dependency.locked_version = item.version
|
429
|
+
|
430
|
+
hash[item.name] = dependency
|
431
|
+
hash
|
254
432
|
end
|
255
433
|
end
|
256
434
|
|
257
|
-
|
258
|
-
|
435
|
+
# Find a given dependency in the graph.
|
436
|
+
#
|
437
|
+
# @param [Dependency, String]
|
438
|
+
# the name/dependency to find
|
439
|
+
#
|
440
|
+
# @return [GraphItem, nil]
|
441
|
+
# the item for the name
|
442
|
+
def find(dependency)
|
443
|
+
@graph[Dependency.name(dependency)]
|
259
444
|
end
|
260
445
|
|
261
|
-
#
|
262
|
-
# table).
|
446
|
+
# Find if the given lock exists?
|
263
447
|
#
|
264
|
-
# @param [
|
265
|
-
# the dependency to find
|
448
|
+
# @param [Dependency, String]
|
449
|
+
# the name/dependency to find
|
266
450
|
#
|
267
|
-
# @return [
|
451
|
+
# @return [true, false]
|
452
|
+
def lock?(dependency)
|
453
|
+
!find(dependency).nil?
|
454
|
+
end
|
455
|
+
alias_method :has_lock?, :lock?
|
456
|
+
|
457
|
+
# Determine if this graph contains the given dependency. This method is
|
458
|
+
# used by the lockfile when adding or removing dependencies to see if a
|
459
|
+
# dependency can be safely removed.
|
460
|
+
#
|
461
|
+
# @param [Dependency, String] dependency
|
462
|
+
# the name/dependency to find
|
463
|
+
def dependency?(dependency)
|
464
|
+
@graph.values.any? do |item|
|
465
|
+
item.dependencies.key?(Dependency.name(dependency))
|
466
|
+
end
|
467
|
+
end
|
468
|
+
alias_method :has_dependency?, :dependency?
|
469
|
+
|
470
|
+
# Add each a new {GraphItem} to the graph.
|
471
|
+
#
|
472
|
+
# @param [#to_s] name
|
268
473
|
# the name of the cookbook
|
269
|
-
|
270
|
-
|
474
|
+
# @param [#to_s] version
|
475
|
+
# the version of the lock
|
476
|
+
#
|
477
|
+
# @return [GraphItem]
|
478
|
+
def add(name, version)
|
479
|
+
@graph[name.to_s] = GraphItem.new(name, version)
|
271
480
|
end
|
272
481
|
|
273
|
-
#
|
482
|
+
# Recursively remove any dependencies from the graph unless they exist as
|
483
|
+
# top-level dependencies or nested dependencies.
|
274
484
|
#
|
275
|
-
# @
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
#
|
280
|
-
# @param [Berkshelf::Berksfile] berksfile
|
281
|
-
# the associated berksfile
|
282
|
-
# @param [String] content
|
283
|
-
# the string content read from a legacy lockfile
|
284
|
-
def parse(berksfile, content)
|
285
|
-
dependencies = {}.tap do |hash|
|
286
|
-
content.split("\n").each do |line|
|
287
|
-
next if line.empty?
|
288
|
-
source = new(berksfile, line)
|
289
|
-
hash[source.name] = source.options
|
290
|
-
end
|
291
|
-
end
|
485
|
+
# @param [Dependency, String] dependency
|
486
|
+
# the name/dependency to remove
|
487
|
+
def remove(dependency)
|
488
|
+
name = Dependency.name(dependency)
|
292
489
|
|
293
|
-
|
294
|
-
dependencies: dependencies,
|
295
|
-
}
|
296
|
-
end
|
490
|
+
return if @lockfile.dependency?(name) || dependency?(name)
|
297
491
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
492
|
+
# Grab the nested dependencies for this particular entry so we can
|
493
|
+
# recurse and try to remove them from the graph.
|
494
|
+
locked = @graph[name]
|
495
|
+
nested_dependencies = locked && locked.dependencies.keys || []
|
496
|
+
|
497
|
+
# Now delete the entry
|
498
|
+
@graph.delete(name)
|
499
|
+
|
500
|
+
# Recursively try to delete the remaining dependencies for this item
|
501
|
+
nested_dependencies.each(&method(:remove))
|
502
|
+
end
|
503
|
+
|
504
|
+
# Update the graph with the given cookbooks. This method destroys the
|
505
|
+
# existing dependency graph with this new result!
|
506
|
+
#
|
507
|
+
# @param [Array<CachedCookbook>]
|
508
|
+
# the list of cookbooks to populate the graph with
|
509
|
+
def update(cookbooks)
|
510
|
+
@graph = {}
|
511
|
+
|
512
|
+
cookbooks.each do |cookbook|
|
513
|
+
@graph[cookbook.cookbook_name.to_s] = GraphItem.new(
|
514
|
+
cookbook.name,
|
515
|
+
cookbook.version,
|
516
|
+
cookbook.dependencies,
|
517
|
+
)
|
518
|
+
end
|
519
|
+
end
|
307
520
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
521
|
+
# Write the contents of the graph to the lockfile format.
|
522
|
+
#
|
523
|
+
# The resulting format looks like:
|
524
|
+
#
|
525
|
+
# GRAPH
|
526
|
+
# apache2 (1.8.14)
|
527
|
+
# yum-epel (0.2.0)
|
528
|
+
# yum (~> 3.0)
|
529
|
+
#
|
530
|
+
# @example lockfile.graph.to_lock #=> "GRAPH\n apache2 (1.18.14)\n..."
|
531
|
+
#
|
532
|
+
# @return [String]
|
533
|
+
#
|
534
|
+
def to_lock
|
535
|
+
out = "#{Lockfile::GRAPH}\n"
|
536
|
+
@graph.sort.each do |name, item|
|
537
|
+
out << " #{name} (#{item.version})\n"
|
538
|
+
|
539
|
+
unless item.dependencies.empty?
|
540
|
+
item.dependencies.sort.each do |name, constraint|
|
541
|
+
out << " #{name} (#{constraint})\n"
|
312
542
|
end
|
543
|
+
end
|
313
544
|
end
|
314
545
|
|
315
|
-
|
316
|
-
|
317
|
-
|
546
|
+
out
|
547
|
+
end
|
548
|
+
|
549
|
+
private
|
318
550
|
|
551
|
+
# A single item inside the graph.
|
552
|
+
class GraphItem
|
553
|
+
# The name of the cookbook that corresponds to this graph item.
|
554
|
+
#
|
319
555
|
# @return [String]
|
320
|
-
# the name of
|
556
|
+
# the name of the cookbook
|
321
557
|
attr_reader :name
|
322
558
|
|
323
|
-
#
|
324
|
-
#
|
325
|
-
|
559
|
+
# The locked version for this graph item.
|
560
|
+
#
|
561
|
+
# @return [String]
|
562
|
+
# the locked version of the graph item (as a string)
|
563
|
+
attr_reader :version
|
326
564
|
|
327
|
-
#
|
565
|
+
# The list of dependencies and their constraints.
|
328
566
|
#
|
329
|
-
# @
|
330
|
-
# the
|
331
|
-
|
332
|
-
|
333
|
-
|
567
|
+
# @return [Hash<String, String>]
|
568
|
+
# the list of dependencies for this graph item, where the key
|
569
|
+
# corresponds to the name of the dependency and the value is the
|
570
|
+
# version constraint.
|
571
|
+
attr_reader :dependencies
|
572
|
+
|
573
|
+
# Create a new graph item.
|
574
|
+
def initialize(name, version, dependencies = {})
|
575
|
+
@name = name.to_s
|
576
|
+
@version = version.to_s
|
577
|
+
@dependencies = dependencies
|
334
578
|
end
|
335
579
|
|
336
|
-
#
|
580
|
+
# Add a new dependency to the list.
|
337
581
|
#
|
338
|
-
# @param [
|
339
|
-
# the name
|
340
|
-
# @
|
341
|
-
# the
|
342
|
-
def
|
343
|
-
@name
|
344
|
-
@options = manipulate(options)
|
582
|
+
# @param [#to_s] name
|
583
|
+
# the name to use
|
584
|
+
# @param [#to_s] constraint
|
585
|
+
# the version constraint to use
|
586
|
+
def add_dependency(name, constraint)
|
587
|
+
@dependencies[name.to_s] = constraint.to_s
|
345
588
|
end
|
346
|
-
|
347
|
-
private
|
348
|
-
|
349
|
-
# Perform various manipulations on the hash.
|
350
|
-
#
|
351
|
-
# @param [Hash] options
|
352
|
-
def manipulate(options = {})
|
353
|
-
if options[:path]
|
354
|
-
options[:path] = berksfile.find(name).instance_variable_get(:@options)[:path] || options[:path]
|
355
|
-
end
|
356
|
-
options
|
357
|
-
end
|
358
589
|
end
|
590
|
+
end
|
359
591
|
end
|
360
592
|
end
|