berkshelf 0.4.0.rc4 → 0.4.0

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.
Files changed (56) hide show
  1. data/.gitignore +2 -0
  2. data/Guardfile +6 -3
  3. data/features/default_locations.feature +122 -0
  4. data/features/install.feature +20 -4
  5. data/features/lockfile.feature +1 -6
  6. data/features/update.feature +2 -3
  7. data/generator_files/Berksfile.erb +2 -0
  8. data/generator_files/gitignore.erb +6 -0
  9. data/lib/berkshelf.rb +6 -10
  10. data/lib/berkshelf/berksfile.rb +203 -14
  11. data/lib/berkshelf/cached_cookbook.rb +5 -1
  12. data/lib/berkshelf/cli.rb +4 -0
  13. data/lib/berkshelf/cookbook_source.rb +49 -91
  14. data/lib/berkshelf/cookbook_store.rb +2 -0
  15. data/lib/berkshelf/downloader.rb +71 -51
  16. data/lib/berkshelf/errors.rb +7 -3
  17. data/lib/berkshelf/formatters.rb +6 -6
  18. data/lib/berkshelf/location.rb +171 -0
  19. data/lib/berkshelf/locations/chef_api_location.rb +252 -0
  20. data/lib/berkshelf/locations/git_location.rb +76 -0
  21. data/lib/berkshelf/locations/path_location.rb +38 -0
  22. data/lib/berkshelf/locations/site_location.rb +150 -0
  23. data/lib/berkshelf/lockfile.rb +2 -2
  24. data/lib/berkshelf/resolver.rb +12 -15
  25. data/lib/berkshelf/uploader.rb +2 -9
  26. data/lib/berkshelf/version.rb +1 -1
  27. data/spec/fixtures/lockfile_spec/without_lock/.gitkeep +0 -0
  28. data/spec/support/chef_api.rb +7 -1
  29. data/spec/unit/berkshelf/berksfile_spec.rb +157 -12
  30. data/spec/unit/berkshelf/cached_cookbook_spec.rb +19 -0
  31. data/spec/unit/berkshelf/cookbook_generator_spec.rb +1 -0
  32. data/spec/unit/berkshelf/cookbook_source_spec.rb +25 -35
  33. data/spec/unit/berkshelf/cookbook_store_spec.rb +3 -3
  34. data/spec/unit/berkshelf/downloader_spec.rb +171 -43
  35. data/spec/unit/berkshelf/formatters_spec.rb +13 -16
  36. data/spec/unit/berkshelf/{cookbook_source/location_spec.rb → location_spec.rb} +10 -10
  37. data/spec/unit/berkshelf/{cookbook_source → locations}/chef_api_location_spec.rb +4 -4
  38. data/spec/unit/berkshelf/{cookbook_source → locations}/git_location_spec.rb +8 -8
  39. data/spec/unit/berkshelf/{cookbook_source → locations}/path_location_spec.rb +5 -5
  40. data/spec/unit/berkshelf/{cookbook_source → locations}/site_location_spec.rb +17 -3
  41. data/spec/unit/berkshelf/lockfile_spec.rb +26 -17
  42. data/spec/unit/berkshelf/resolver_spec.rb +6 -5
  43. data/spec/unit/berkshelf/uploader_spec.rb +6 -4
  44. metadata +27 -31
  45. data/lib/berkshelf/cookbook_source/chef_api_location.rb +0 -256
  46. data/lib/berkshelf/cookbook_source/git_location.rb +0 -78
  47. data/lib/berkshelf/cookbook_source/location.rb +0 -167
  48. data/lib/berkshelf/cookbook_source/path_location.rb +0 -40
  49. data/lib/berkshelf/cookbook_source/site_location.rb +0 -149
  50. data/lib/berkshelf/dsl.rb +0 -45
  51. data/lib/berkshelf/tx_result.rb +0 -12
  52. data/lib/berkshelf/tx_result_set.rb +0 -37
  53. data/spec/fixtures/lockfile_spec/without_lock/Berksfile.lock +0 -5
  54. data/spec/unit/berkshelf/dsl_spec.rb +0 -42
  55. data/spec/unit/berkshelf/tx_result_set_spec.rb +0 -77
  56. data/spec/unit/berkshelf/tx_result_spec.rb +0 -21
@@ -94,7 +94,6 @@ module Berkshelf
94
94
  attr_reader :manifest
95
95
 
96
96
  def_delegator :@metadata, :version
97
- def_delegator :@metadata, :dependencies
98
97
 
99
98
  def initialize(name, path, metadata)
100
99
  @cookbook_name = name
@@ -125,6 +124,11 @@ module Berkshelf
125
124
  "#{cookbook_name}-#{version}"
126
125
  end
127
126
 
127
+ # @return [Hash]
128
+ def dependencies
129
+ metadata.recommendations.merge(metadata.dependencies)
130
+ end
131
+
128
132
  # @return [Hash]
129
133
  # an hash containing the checksums and expanded file paths of all of the
130
134
  # files found in the instance of CachedCookbook
@@ -59,6 +59,10 @@ module Berkshelf
59
59
  banner: "PATH"
60
60
  desc "install", "Install the Cookbooks specified by a Berksfile or a Berksfile.lock."
61
61
  def install
62
+ unless options[:shims].nil?
63
+ options[:shims] = File.expand_path(options[:shims])
64
+ end
65
+
62
66
  berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
63
67
  berksfile.install(options)
64
68
  end
@@ -2,7 +2,7 @@ module Berkshelf
2
2
  # @author Jamie Winsor <jamie@vialstudios.com>
3
3
  class CookbookSource
4
4
  class << self
5
- @@valid_options = [:group, :locked_version]
5
+ @@valid_options = [:constraint, :locations, :group, :locked_version]
6
6
  @@location_keys = Hash.new
7
7
 
8
8
  # Returns an array of valid options to pass to the initializer
@@ -48,83 +48,62 @@ module Berkshelf
48
48
 
49
49
  @@location_keys
50
50
  end
51
+
52
+ def validate_options(options)
53
+ invalid_options = (options.keys - valid_options)
54
+
55
+ unless invalid_options.empty?
56
+ invalid_options.collect! { |opt| "'#{opt}'" }
57
+ raise InternalError, "Invalid options for Cookbook Source: #{invalid_options.join(', ')}."
58
+ end
59
+
60
+ true
61
+ end
51
62
  end
52
63
 
53
64
  extend Forwardable
54
65
 
55
- # JW TODO: Move locations out of CookbookSource namespace.
56
- # Move all locations into berkshelf/locations/*
57
- # Autorequire all items in berkshelf/locations/
58
- require 'berkshelf/cookbook_source/location'
59
- require 'berkshelf/cookbook_source/site_location'
60
- require 'berkshelf/cookbook_source/git_location'
61
- require 'berkshelf/cookbook_source/path_location'
62
- require 'berkshelf/cookbook_source/chef_api_location'
63
-
64
66
  attr_reader :name
65
- alias_method :to_s, :name
66
-
67
67
  attr_reader :version_constraint
68
68
  attr_reader :groups
69
69
  attr_reader :location
70
70
  attr_accessor :cached_cookbook
71
71
 
72
- def_delegator :@location, :downloaded?
73
-
74
- # @overload initialize(name, version_constraint, options = {})
75
- # @param [#to_s] name
76
- # @param [#to_s] version_constraint
77
- # @param [Hash] options
78
- #
79
- # @option options [String] :git
80
- # the Git URL to clone
81
- # @option options [String] :site
82
- # a URL pointing to a community API endpoint
83
- # @option options [String] :path
84
- # a filepath to the cookbook on your local disk
85
- # @option options [Symbol, Array] :group
86
- # the group or groups that the cookbook belongs to
87
- # @option options [String] :ref
88
- # the commit hash or an alias to a commit hash to clone
89
- # @option options [String] :branch
90
- # same as ref
91
- # @option options [String] :tag
92
- # same as tag
93
- # @option options [String] :locked_version
94
- # @overload initialize(name, options = {})
95
- # @param [#to_s] name
96
- # @param [Hash] options
72
+ # @param [String] name
73
+ # @param [Hash] options
97
74
  #
98
- # @option options [String] :git
99
- # the Git URL to clone
100
- # @option options [String] :site
101
- # a URL pointing to a community API endpoint
102
- # @option options [String] :path
103
- # a filepath to the cookbook on your local disk
104
- # @option options [Symbol, Array] :group
105
- # the group or groups that the cookbook belongs to
106
- # @option options [String] :ref
107
- # the commit hash or an alias to a commit hash to clone
108
- # @option options [String] :branch
109
- # same as ref
110
- # @option options [String] :tag
111
- # same as tag
112
- # @option options [String] :locked_version
113
- def initialize(*args)
114
- options = args.last.is_a?(Hash) ? args.pop : {}
115
- name, constraint = args
116
-
75
+ # @option options [String, Solve::Constraint] constraint
76
+ # version constraint to resolve for this source
77
+ # @option options [String] :git
78
+ # the Git URL to clone
79
+ # @option options [String] :site
80
+ # a URL pointing to a community API endpoint
81
+ # @option options [String] :path
82
+ # a filepath to the cookbook on your local disk
83
+ # @option options [Symbol, Array] :group
84
+ # the group or groups that the cookbook belongs to
85
+ # @option options [String] :ref
86
+ # the commit hash or an alias to a commit hash to clone
87
+ # @option options [String] :branch
88
+ # same as ref
89
+ # @option options [String] :tag
90
+ # same as tag
91
+ # @option options [String] :locked_version
92
+ def initialize(name, options = {})
117
93
  @name = name
118
- @version_constraint = Solve::Constraint.new(constraint || ">= 0.0.0")
94
+ @version_constraint = Solve::Constraint.new(options[:constraint] || ">= 0.0.0")
119
95
  @groups = []
120
96
  @cached_cookbook = nil
97
+ @location = nil
121
98
 
122
- validate_options(options)
99
+ self.class.validate_options(options)
123
100
 
124
- @location = Location.init(name, version_constraint, options)
101
+ unless (options.keys & self.class.location_keys.keys).empty?
102
+ @location = Location.init(name, version_constraint, options)
103
+ end
125
104
 
126
105
  if @location.is_a?(PathLocation)
127
- @cached_cookbook = CachedCookbook.from_path(@location.path)
106
+ @cached_cookbook = CachedCookbook.from_path(location.path)
128
107
  end
129
108
 
130
109
  @locked_version = Solve::Version.new(options[:locked_version]) if options[:locked_version]
@@ -141,22 +120,12 @@ module Berkshelf
141
120
  end
142
121
  end
143
122
 
144
- # @param [String] destination
145
- # destination to download to
146
- #
147
- # @return [Array]
148
- # An array containing the status at index 0 and local path or error message in index 1
123
+ # Returns true if the cookbook source has already been downloaded. A cookbook
124
+ # source is downloaded when a cached cookbooked is present.
149
125
  #
150
- # Example:
151
- # [ :ok, "/tmp/nginx" ]
152
- # [ :error, "Cookbook 'sparkle_motion' not found at site: http://cookbooks.opscode.com/api/v1/cookbooks" ]
153
- def download(destination)
154
- self.cached_cookbook = location.download(destination)
155
-
156
- [ :ok, self.cached_cookbook ]
157
- rescue CookbookNotFound => e
158
- self.cached_cookbook = nil
159
- [ :error, e.message ]
126
+ # @return [Boolean]
127
+ def downloaded?
128
+ !self.cached_cookbook.nil?
160
129
  end
161
130
 
162
131
  def has_group?(group)
@@ -167,21 +136,10 @@ module Berkshelf
167
136
  @locked_version || cached_cookbook.version
168
137
  end
169
138
 
170
- private
171
-
172
- def set_local_path(path)
173
- @local_path = path
174
- end
175
-
176
- def validate_options(options)
177
- invalid_options = options.keys - self.class.valid_options
178
-
179
- unless invalid_options.empty?
180
- invalid_options.collect! { |opt| "'#{opt}'" }
181
- raise InternalError, "Invalid options for Cookbook Source: #{invalid_options.join(', ')}."
182
- end
183
-
184
- true
185
- end
139
+ def to_s
140
+ msg = "#{self.name} (#{self.version_constraint}) groups: #{self.groups}"
141
+ msg << " location: #{self.location}" if self.location
142
+ msg
143
+ end
186
144
  end
187
145
  end
@@ -3,6 +3,8 @@ require 'fileutils'
3
3
  module Berkshelf
4
4
  # @author Jamie Winsor <jamie@vialstudios.com>
5
5
  class CookbookStore
6
+ # @return [String]
7
+ # filepath to where cookbooks are stored
6
8
  attr_reader :storage_path
7
9
 
8
10
  # Create a new instance of CookbookStore with the given
@@ -3,80 +3,100 @@ module Berkshelf
3
3
  class Downloader
4
4
  extend Forwardable
5
5
 
6
+ DEFAULT_LOCATIONS = [
7
+ {
8
+ type: :site,
9
+ value: Location::OPSCODE_COMMUNITY_API,
10
+ options: Hash.new
11
+ }
12
+ ]
13
+
14
+ # @return [String]
15
+ # a filepath to download cookbook sources to
6
16
  attr_reader :cookbook_store
7
- attr_reader :queue
8
17
 
9
18
  def_delegators :@cookbook_store, :storage_path
10
19
 
11
- def initialize(cookbook_store)
20
+ # @option options [Array<Hash>] locations
21
+ def initialize(cookbook_store, options = {})
12
22
  @cookbook_store = cookbook_store
13
- @queue = []
23
+ @locations = options.fetch(:locations, Array.new)
14
24
  end
15
25
 
16
- # Add a CookbookSource to the downloader's queue
17
- #
18
- # @param [Berkshelf::CookbookSource] source
19
- #
20
- # @return [Array<Berkshelf::CookbookSource>]
21
- # the queue - an array of Berkshelf::CookbookSources
22
- def enqueue(source)
23
- unless validate_source(source)
24
- raise ArgumentError, "Invalid CookbookSource: can only enqueue valid instances of CookbookSource."
25
- end
26
-
27
- @queue << source
26
+ # @return [Array<Hash>]
27
+ # an Array of Hashes representing each default location that can be used to attempt
28
+ # to download cookbook sources which do not have an explicit location. An array of default locations will
29
+ # be used if no locations are explicitly added by the {#add_location} function.
30
+ def locations
31
+ @locations.empty? ? DEFAULT_LOCATIONS : @locations
28
32
  end
29
33
 
30
- # Remove a CookbookSource from the downloader's queue
34
+ # Create a location hash and add it to the end of the array of locations.
31
35
  #
32
- # @param [Berkshelf::CookbookSource] source
36
+ # subject.add_location(:chef_api, "http://chef:8080", node_name: "reset", client_key: "/Users/reset/.chef/reset.pem") =>
37
+ # [ { type: :chef_api, value: "http://chef:8080/", node_name: "reset", client_key: "/Users/reset/.chef/reset.pem" } ]
33
38
  #
34
- # @return [Berkshelf::CookbookSource]
35
- # the CookbookSource removed from the queue
36
- def dequeue(source)
37
- @queue.delete(source)
38
- end
39
-
40
- # Download each CookbookSource in the queue. Upon successful download
41
- # of a CookbookSource it is removed from the queue. If a CookbookSource
42
- # fails to download it remains in the queue.
39
+ # @param [Symbol] type
40
+ # @param [String, Symbol] value
41
+ # @param [Hash] options
43
42
  #
44
- # @return [TXResultSet]
45
- def download_all
46
- results = TXResultSet.new
47
-
48
- queue.each do |source|
49
- results.add_result download(source)
43
+ # @return [Hash]
44
+ def add_location(type, value, options = {})
45
+ if has_location?(type, value)
46
+ raise DuplicateLocationDefined, "A default '#{type}' location with the value '#{value}' is already defined"
50
47
  end
51
-
52
- results.success.each { |result| dequeue(result.source) }
53
48
 
54
- results
49
+ @locations.push(type: type, value: value, options: options)
55
50
  end
56
51
 
57
- # Downloads the given CookbookSource
58
- #
59
- # @param [CookbookSource] source
60
- # the source to download
52
+ # Checks the list of default locations if a location of the given type and value has already
53
+ # been added and returns true or false.
61
54
  #
62
- # @return [TXResult]
63
- def download(source)
64
- status, message = source.download(storage_path)
65
- TXResult.new(status, message, source)
55
+ # @return [Boolean]
56
+ def has_location?(type, value)
57
+ !@locations.select { |loc| loc[:type] == type && loc[:value] == value }.empty?
66
58
  end
67
59
 
68
- # Downloads the given CookbookSource. Raises a DownloadFailure error
69
- # if the download was not successful.
60
+ # Downloads the given CookbookSource. If the given source does not contain a value for {CookbookSource#location}
61
+ # the default locations of this downloader will be used to attempt to retrieve the source.
70
62
  #
71
63
  # @param [CookbookSource] source
72
64
  # the source to download
73
65
  #
74
- # @return [TXResult]
75
- def download!(source)
76
- result = download(source)
77
- raise DownloadFailure, result.message if result.failed?
78
-
79
- result
66
+ # @return [Array]
67
+ # an array containing the downloaded CachedCookbook and the Location used to download the cookbook
68
+ def download(source)
69
+ cached_cookbook, location = if source.location
70
+ [ source.location.download(storage_path), source.location ]
71
+ else
72
+ cached_cookbook = nil
73
+ location = nil
74
+
75
+ locations.each do |loc|
76
+ location = Location.init(
77
+ source.name,
78
+ source.version_constraint,
79
+ loc[:options].merge(loc[:type] => loc[:value])
80
+ )
81
+ begin
82
+ cached_cookbook = location.download(storage_path)
83
+ break
84
+ rescue
85
+ cached_cookbook, location = nil
86
+ next
87
+ end
88
+ end
89
+
90
+ if cached_cookbook.nil?
91
+ raise CookbookNotFound, "Cookbook '#{source.name}' not found in any of the default locations"
92
+ end
93
+
94
+ [ cached_cookbook, location ]
95
+ end
96
+
97
+ source.cached_cookbook = cached_cookbook
98
+
99
+ [ cached_cookbook, location ]
80
100
  end
81
101
 
82
102
  private
@@ -12,11 +12,15 @@ module Berkshelf
12
12
  end
13
13
 
14
14
  class InternalError < BerkshelfError; status_code(99); end
15
- class MethodNotImplmentedError < ::Berkshelf::InternalError ; end
16
-
15
+ class AbstractFunction < InternalError
16
+ def to_s
17
+ "Function must be implemented on includer"
18
+ end
19
+ end
20
+
17
21
  class BerksfileNotFound < BerkshelfError; status_code(100); end
18
22
  class NoVersionForConstraints < BerkshelfError; status_code(101); end
19
- class DownloadFailure < BerkshelfError; status_code(102); end
23
+ class DuplicateLocationDefined < BerkshelfError; status_code(102); end
20
24
  class CookbookNotFound < BerkshelfError; status_code(103); end
21
25
  class GitError < BerkshelfError
22
26
  status_code(104)
@@ -10,27 +10,27 @@ module Berkshelf
10
10
  end
11
11
 
12
12
  def install(cookbook, version, location)
13
- raise MethodNotImplmentedError, "#install must be implemented on #{self.class}"
13
+ raise AbstractFunction, "#install must be implemented on #{self.class}"
14
14
  end
15
15
 
16
16
  def use(cookbook, version, path = nil)
17
- raise MethodNotImplmentedError, "#install must be implemented on #{self.class}"
17
+ raise AbstractFunction, "#install must be implemented on #{self.class}"
18
18
  end
19
19
 
20
20
  def upload(cookbook, version, chef_server_url)
21
- raise MethodNotImplmentedError, "#upload must be implemented on #{self.class}"
21
+ raise AbstractFunction, "#upload must be implemented on #{self.class}"
22
22
  end
23
23
 
24
24
  def shims_written(directory)
25
- raise MethodNotImplmentedError, "#shims_written must be implemented on #{self.class}"
25
+ raise AbstractFunction, "#shims_written must be implemented on #{self.class}"
26
26
  end
27
27
 
28
28
  def msg(message)
29
- raise MethodNotImplmentedError, "#msg must be implemented on #{self.class}"
29
+ raise AbstractFunction, "#msg must be implemented on #{self.class}"
30
30
  end
31
31
 
32
32
  def error(message)
33
- raise MethodNotImplmentedError, "#error must be implemented on #{self.class}"
33
+ raise AbstractFunction, "#error must be implemented on #{self.class}"
34
34
  end
35
35
  end
36
36
  end
@@ -0,0 +1,171 @@
1
+ module Berkshelf
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ module Location
4
+ OPSCODE_COMMUNITY_API = 'http://cookbooks.opscode.com/api/v1/cookbooks'.freeze
5
+
6
+ module ClassMethods
7
+ # Register the location key for the including source location with CookbookSource
8
+ #
9
+ # @param [Symbol] key
10
+ def location_key(key)
11
+ CookbookSource.add_location_key(key, self)
12
+ end
13
+
14
+ # Register a valid option or multiple options with the CookbookSource class
15
+ #
16
+ # @param [Symbol] opts
17
+ def valid_options(*opts)
18
+ Array(opts).each do |opt|
19
+ CookbookSource.add_valid_option(opt)
20
+ end
21
+ end
22
+
23
+ # Returns an array where the first element is string representing the best version
24
+ # for the given constraint and the second element is the URI to where the corresponding
25
+ # version of the Cookbook can be downloaded from
26
+ #
27
+ # @example:
28
+ # constraint = Solve::Constraint.new("~> 0.101.2")
29
+ # versions = {
30
+ # "1.0.0" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/1_0_0",
31
+ # "2.0.0" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/2_0_0"
32
+ # }
33
+ #
34
+ # subject.solve_for_constraint(versions, constraint) =>
35
+ # [ "2.0.0", "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/2_0_0" ]
36
+ #
37
+ # @param [String, Solve::Constraint] constraint
38
+ # version constraint to solve for
39
+ #
40
+ # @param [Hash] versions
41
+ # a hash where the keys are a string representing a cookbook version and the values
42
+ # are the download URL for the cookbook version.
43
+ #
44
+ # @return [Array, nil]
45
+ def solve_for_constraint(constraint, versions)
46
+ graph = Solve::Graph.new
47
+ name = "none"
48
+
49
+ versions.each do |version, uri|
50
+ graph.artifacts(name, version)
51
+ end
52
+
53
+ graph.demands(name, constraint)
54
+ result = Solve.it(graph)
55
+
56
+ return nil if result.nil?
57
+
58
+ version = result[name]
59
+
60
+ [ version, versions[version] ]
61
+ end
62
+ end
63
+
64
+ class << self
65
+ def included(base)
66
+ base.send :extend, ClassMethods
67
+ end
68
+
69
+ # Creates a new instance of a Class implementing Location with the given name and
70
+ # constraint. Which Class to instantiated is determined by the values in the given
71
+ # options Hash. Source Locations have an associated location_key registered with
72
+ # CookbookSource. If your options Hash contains a key matching one of these location_keys
73
+ # then the Class who registered that location_key will be instantiated. If you do not
74
+ # provide an option with a matching location_key a SiteLocation class will be
75
+ # instantiated.
76
+ #
77
+ # @example
78
+ # Location.init("nginx", ">= 0.0.0", git: "git://github.com/RiotGames/artifact-cookbook.git") =>
79
+ # instantiates a GitLocation
80
+ #
81
+ # Location.init("nginx", ">= 0.0.0", path: "/Users/reset/code/nginx-cookbook") =>
82
+ # instantiates a PathLocation
83
+ #
84
+ # Location.init("nginx", ">= 0.0.0", site: "http://cookbooks.opscode.com/api/v1/cookbooks") =>
85
+ # instantiates a SiteLocation
86
+ #
87
+ # Location.init("nginx", ">= 0.0.0", chef_api: "https://api.opscode.com/organizations/vialstudios") =>
88
+ # instantiates a ChefAPILocation
89
+ #
90
+ # Location.init("nginx", ">= 0.0.0") =>
91
+ # instantiates a SiteLocation
92
+ #
93
+ # @param [String] name
94
+ # @param [String, Solve::Constraint] constraint
95
+ # @param [Hash] options
96
+ #
97
+ # @return [SiteLocation, PathLocation, GitLocation, ChefAPILocation]
98
+ def init(name, constraint, options = {})
99
+ klass = klass_from_options(options)
100
+
101
+ klass.new(name, constraint, options)
102
+ end
103
+
104
+ private
105
+
106
+ def klass_from_options(options)
107
+ location_keys = (options.keys & CookbookSource.location_keys.keys)
108
+ if location_keys.length > 1
109
+ location_keys.collect! { |opt| "'#{opt}'" }
110
+ raise InternalError, "Only one location key (#{CookbookSource.location_keys.keys.join(', ')}) may be specified. You gave #{location_keys.join(', ')}."
111
+ end
112
+
113
+ if location_keys.empty?
114
+ SiteLocation
115
+ else
116
+ CookbookSource.location_keys[location_keys.first]
117
+ end
118
+ end
119
+ end
120
+
121
+ attr_reader :name
122
+ attr_reader :version_constraint
123
+
124
+ # @param [#to_s] name
125
+ # @param [Solve::Constraint] version_constraint
126
+ # @param [Hash] options
127
+ def initialize(name, version_constraint, options = {})
128
+ @name = name
129
+ @version_constraint = version_constraint
130
+ @downloaded_status = false
131
+ end
132
+
133
+ # @param [#to_s] destination
134
+ #
135
+ # @return [Berkshelf::CachedCookbook]
136
+ def download(destination)
137
+ raise AbstractFunction
138
+ end
139
+
140
+ # @return [Boolean]
141
+ def downloaded?
142
+ @downloaded_status
143
+ end
144
+
145
+ # Ensures that the given CachedCookbook satisfies the constraint
146
+ #
147
+ # @param [CachedCookbook] cached_cookbook
148
+ #
149
+ # @raise [ConstraintNotSatisfied] if the CachedCookbook does not satisfy the version constraint of
150
+ # this instance of Location.
151
+ #
152
+ # @return [Boolean]
153
+ def validate_cached(cached_cookbook)
154
+ unless version_constraint.satisfies?(cached_cookbook.version)
155
+ raise ConstraintNotSatisfied, "A cookbook satisfying '#{name}' (#{version_constraint}) not found at #{self}"
156
+ end
157
+
158
+ true
159
+ end
160
+
161
+ private
162
+
163
+ def set_downloaded_status(state)
164
+ @downloaded_status = state
165
+ end
166
+ end
167
+ end
168
+
169
+ Dir["#{File.dirname(__FILE__)}/locations/*.rb"].sort.each do |path|
170
+ require "berkshelf/locations/#{File.basename(path, '.rb')}"
171
+ end