berkshelf 0.3.7 → 0.4.0.rc1
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.
- data/.gitignore +2 -1
- data/README.md +8 -0
- data/Thorfile +2 -2
- data/berkshelf.gemspec +1 -1
- data/features/install.feature +102 -2
- data/features/lockfile.feature +1 -1
- data/features/step_definitions/chef_server_steps.rb +8 -0
- data/features/step_definitions/cli_steps.rb +1 -1
- data/features/step_definitions/filesystem_steps.rb +12 -0
- data/features/support/env.rb +8 -10
- data/features/update.feature +1 -1
- data/lib/berkshelf.rb +19 -1
- data/lib/berkshelf/berksfile.rb +18 -6
- data/lib/berkshelf/cli.rb +3 -8
- data/lib/berkshelf/cookbook_source.rb +108 -23
- data/lib/berkshelf/cookbook_source/chef_api_location.rb +256 -0
- data/lib/berkshelf/cookbook_source/git_location.rb +14 -2
- data/lib/berkshelf/cookbook_source/location.rb +119 -2
- data/lib/berkshelf/cookbook_source/path_location.rb +6 -1
- data/lib/berkshelf/cookbook_source/site_location.rb +36 -105
- data/lib/berkshelf/cookbook_store.rb +7 -12
- data/lib/berkshelf/dsl.rb +1 -1
- data/lib/berkshelf/errors.rb +2 -0
- data/lib/berkshelf/init_generator.rb +6 -6
- data/lib/berkshelf/resolver.rb +8 -34
- data/lib/berkshelf/uploader.rb +7 -7
- data/lib/berkshelf/version.rb +1 -1
- data/spec/fixtures/reset.pem +27 -0
- data/spec/knife.rb.sample +12 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/support/chef_api.rb +32 -0
- data/spec/support/knife.rb +18 -0
- data/spec/unit/berkshelf/cookbook_source/chef_api_location_spec.rb +243 -0
- data/spec/unit/berkshelf/cookbook_source/git_location_spec.rb +2 -2
- data/spec/unit/berkshelf/cookbook_source/location_spec.rb +130 -2
- data/spec/unit/berkshelf/cookbook_source/path_location_spec.rb +2 -2
- data/spec/unit/berkshelf/cookbook_source/site_location_spec.rb +22 -105
- data/spec/unit/berkshelf/cookbook_source_spec.rb +140 -71
- data/spec/unit/berkshelf/cookbook_store_spec.rb +6 -6
- data/spec/unit/berkshelf/resolver_spec.rb +9 -30
- data/spec/unit/berkshelf/uploader_spec.rb +1 -10
- data/spec/unit/berkshelf_spec.rb +21 -1
- metadata +19 -15
- data/features/config.sample.yml +0 -3
@@ -0,0 +1,256 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Berkshelf
|
4
|
+
class CookbookSource
|
5
|
+
# @author Jamie Winsor <jamie@vialstudios.com>
|
6
|
+
class ChefAPILocation
|
7
|
+
class << self
|
8
|
+
# @param [String] node_name
|
9
|
+
#
|
10
|
+
# @return [Boolean]
|
11
|
+
def validate_node_name(node_name)
|
12
|
+
node_name.is_a?(String) && !node_name.empty?
|
13
|
+
end
|
14
|
+
|
15
|
+
# @raise [InvalidChefAPILocation]
|
16
|
+
#
|
17
|
+
# @see validate_node_name
|
18
|
+
def validate_node_name!(node_name)
|
19
|
+
unless validate_node_name(node_name)
|
20
|
+
raise InvalidChefAPILocation
|
21
|
+
end
|
22
|
+
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [String] client_key
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
def validate_client_key(client_key)
|
30
|
+
File.exists?(client_key)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @raise [InvalidChefAPILocation]
|
34
|
+
#
|
35
|
+
# @see validate_client_key
|
36
|
+
def validate_client_key!(client_key)
|
37
|
+
unless validate_client_key(client_key)
|
38
|
+
raise InvalidChefAPILocation
|
39
|
+
end
|
40
|
+
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [String] uri
|
45
|
+
#
|
46
|
+
# @return [Boolean]
|
47
|
+
def validate_uri(uri)
|
48
|
+
uri =~ URI.regexp(['http', 'https'])
|
49
|
+
end
|
50
|
+
|
51
|
+
# @raise [InvalidChefAPILocation] if the given object is not a String containing a
|
52
|
+
# valid Chef API URI
|
53
|
+
#
|
54
|
+
# @see validate_uri
|
55
|
+
def validate_uri!(uri)
|
56
|
+
unless validate_uri(uri)
|
57
|
+
raise InvalidChefAPILocation, "'#{uri}' is not a valid Chef API URI."
|
58
|
+
end
|
59
|
+
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
include Location
|
65
|
+
|
66
|
+
location_key :chef_api
|
67
|
+
valid_options :node_name, :client_key
|
68
|
+
|
69
|
+
attr_reader :uri
|
70
|
+
attr_reader :node_name
|
71
|
+
attr_reader :client_key
|
72
|
+
|
73
|
+
# @param [#to_s] name
|
74
|
+
# @param [Solve::Constraint] version_constraint
|
75
|
+
# @param [Hash] options
|
76
|
+
#
|
77
|
+
# @option options [String, Symbol] :chef_api
|
78
|
+
# a URL to a Chef API. Alternatively the symbol :knife can be provided
|
79
|
+
# which will instantiate this location with the values found in your
|
80
|
+
# knife configuration.
|
81
|
+
# @option options [String] :node_name
|
82
|
+
# the name of the client to use to communicate with the Chef API.
|
83
|
+
# Default: Chef::Config[:node_name]
|
84
|
+
# @option options [String] :client_key
|
85
|
+
# the filepath to the authentication key for the client
|
86
|
+
# Default: Chef::Config[:client_key]
|
87
|
+
def initialize(name, version_constraint, options = {})
|
88
|
+
@name = name
|
89
|
+
@version_constraint = version_constraint
|
90
|
+
@downloaded_status = false
|
91
|
+
|
92
|
+
validate_options!(options)
|
93
|
+
|
94
|
+
if options[:chef_api] == :knife
|
95
|
+
begin
|
96
|
+
Berkshelf.load_config
|
97
|
+
rescue KnifeConfigNotFound => e
|
98
|
+
raise KnifeConfigNotFound, "A Knife config is required when ':knife' is given for the value of a 'chef_api' location. #{e}"
|
99
|
+
end
|
100
|
+
@node_name = Chef::Config[:node_name]
|
101
|
+
@client_key = Chef::Config[:client_key]
|
102
|
+
@uri = Chef::Config[:chef_server_url]
|
103
|
+
else
|
104
|
+
@node_name = options[:node_name]
|
105
|
+
@client_key = options[:client_key]
|
106
|
+
@uri = options[:chef_api]
|
107
|
+
end
|
108
|
+
|
109
|
+
@rest = Chef::REST.new(uri, node_name, client_key)
|
110
|
+
end
|
111
|
+
|
112
|
+
# @param [#to_s] destination
|
113
|
+
#
|
114
|
+
# @return [Berkshelf::CachedCookbook]
|
115
|
+
def download(destination)
|
116
|
+
version, uri = target_version
|
117
|
+
cookbook = rest.get_rest(uri)
|
118
|
+
|
119
|
+
scratch = download_files(cookbook.manifest)
|
120
|
+
|
121
|
+
cb_path = File.join(destination, "#{name}-#{version}")
|
122
|
+
FileUtils.mv(scratch, cb_path, force: true)
|
123
|
+
|
124
|
+
cached = CachedCookbook.from_store_path(cb_path)
|
125
|
+
validate_cached(cached)
|
126
|
+
|
127
|
+
set_downloaded_status(true)
|
128
|
+
cached
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns a hash representing the cookbook versions on at a Chef API for location's cookbook.
|
132
|
+
# The keys are version strings and the values are URLs to download the cookbook version.
|
133
|
+
#
|
134
|
+
# @example
|
135
|
+
# {
|
136
|
+
# "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2",
|
137
|
+
# "0.101.5" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.5"
|
138
|
+
# }
|
139
|
+
#
|
140
|
+
# @return [Hash]
|
141
|
+
def versions
|
142
|
+
{}.tap do |versions|
|
143
|
+
rest.get_rest("cookbooks/#{name}").each do |name, data|
|
144
|
+
data["versions"].each do |version_info|
|
145
|
+
versions[version_info["version"]] = version_info["url"]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
rescue Net::HTTPServerException => e
|
150
|
+
if e.response.code == "404"
|
151
|
+
raise CookbookNotFound, "Cookbook '#{name}' not found at chef_api: '#{uri}'"
|
152
|
+
else
|
153
|
+
raise
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns an array where the first element is a string representing the latest version of
|
158
|
+
# the Cookbook and the second element is the download URL for that version.
|
159
|
+
#
|
160
|
+
# @example
|
161
|
+
# [ "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2" ]
|
162
|
+
#
|
163
|
+
# @return [Array]
|
164
|
+
def latest_version
|
165
|
+
graph = Solve::Graph.new
|
166
|
+
versions.each do |version, url|
|
167
|
+
graph.artifacts(name, version)
|
168
|
+
end
|
169
|
+
graph.demands(name, ">= 0.0.0")
|
170
|
+
|
171
|
+
version = Solve.it!(graph)[name]
|
172
|
+
|
173
|
+
[ version, versions[version] ]
|
174
|
+
end
|
175
|
+
|
176
|
+
def to_s
|
177
|
+
"chef_api: '#{uri}'"
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
attr_reader :rest
|
183
|
+
|
184
|
+
# Returns an array containing the version and download URL for the cookbook version that
|
185
|
+
# should be downloaded for this location.
|
186
|
+
#
|
187
|
+
# @example
|
188
|
+
# [ "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2" ]
|
189
|
+
#
|
190
|
+
# @return [Array]
|
191
|
+
def target_version
|
192
|
+
if version_constraint
|
193
|
+
solution = self.class.solve_for_constraint(version_constraint, versions)
|
194
|
+
|
195
|
+
unless solution
|
196
|
+
raise NoSolution, "No cookbook version of '#{name}' found at #{self} that would satisfy constraint (#{version_constraint})."
|
197
|
+
end
|
198
|
+
|
199
|
+
solution
|
200
|
+
else
|
201
|
+
latest_version
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Download all of the files in the given manifest to the given destination. If no destination
|
206
|
+
# is provided a temporary directory will be created and the files will be downloaded to there.
|
207
|
+
#
|
208
|
+
# @note
|
209
|
+
# the manifest Hash is the same manifest that you get by sending the manifest message to
|
210
|
+
# an instance of Chef::CookbookVersion.
|
211
|
+
#
|
212
|
+
# @param [Hash] manifest
|
213
|
+
# @param [String] destination
|
214
|
+
#
|
215
|
+
# @return [String]
|
216
|
+
# the path to the directory containing the files
|
217
|
+
def download_files(manifest, destination = Dir.mktmpdir)
|
218
|
+
Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment|
|
219
|
+
next unless manifest.has_key?(segment)
|
220
|
+
manifest[segment].each do |segment_file|
|
221
|
+
dest = File.join(destination, segment_file['path'].gsub('/', File::SEPARATOR))
|
222
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
223
|
+
rest.sign_on_redirect = false
|
224
|
+
tempfile = rest.get_rest(segment_file['url'], true)
|
225
|
+
FileUtils.mv(tempfile.path, dest)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
destination
|
230
|
+
end
|
231
|
+
|
232
|
+
# Validates the options hash given to the constructor.
|
233
|
+
#
|
234
|
+
# @param [Hash] options
|
235
|
+
#
|
236
|
+
# @raise [InvalidChefAPILocation] if any of the options are missing or their values do not
|
237
|
+
# pass validation
|
238
|
+
def validate_options!(options)
|
239
|
+
if options[:chef_api] == :knife
|
240
|
+
return true
|
241
|
+
end
|
242
|
+
|
243
|
+
missing_options = [:node_name, :client_key] - options.keys
|
244
|
+
|
245
|
+
unless missing_options.empty?
|
246
|
+
missing_options.collect! { |opt| "'#{opt}'" }
|
247
|
+
raise InvalidChefAPILocation, "Source '#{name}' is a 'chef_api' location with a URL for it's value but is missing options: #{missing_options.join(', ')}."
|
248
|
+
end
|
249
|
+
|
250
|
+
self.class.validate_node_name!(options[:node_name])
|
251
|
+
self.class.validate_client_key!(options[:client_key])
|
252
|
+
self.class.validate_uri!(options[:chef_api])
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -4,6 +4,9 @@ module Berkshelf
|
|
4
4
|
class GitLocation
|
5
5
|
include Location
|
6
6
|
|
7
|
+
location_key :git
|
8
|
+
valid_options :ref, :branch, :tag
|
9
|
+
|
7
10
|
attr_accessor :uri
|
8
11
|
attr_accessor :branch
|
9
12
|
|
@@ -11,9 +14,18 @@ module Berkshelf
|
|
11
14
|
alias_method :tag, :branch
|
12
15
|
|
13
16
|
# @param [#to_s] name
|
14
|
-
# @param [
|
17
|
+
# @param [Solve::Constraint] version_constraint
|
15
18
|
# @param [Hash] options
|
16
|
-
|
19
|
+
#
|
20
|
+
# @option options [String] :git
|
21
|
+
# the Git URL to clone
|
22
|
+
# @option options [String] :ref
|
23
|
+
# the commit hash or an alias to a commit hash to clone
|
24
|
+
# @option options [String] :branch
|
25
|
+
# same as ref
|
26
|
+
# @option options [String] :tag
|
27
|
+
# same as tag
|
28
|
+
def initialize(name, version_constraint, options = {})
|
17
29
|
@name = name
|
18
30
|
@version_constraint = version_constraint
|
19
31
|
@uri = options[:git]
|
@@ -2,11 +2,128 @@ module Berkshelf
|
|
2
2
|
class CookbookSource
|
3
3
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
4
4
|
module Location
|
5
|
+
module ClassMethods
|
6
|
+
# Register the location key for the including source location with CookbookSource
|
7
|
+
#
|
8
|
+
# @param [Symbol] key
|
9
|
+
def location_key(key)
|
10
|
+
CookbookSource.add_location_key(key, self)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Register a valid option or multiple options with the CookbookSource class
|
14
|
+
#
|
15
|
+
# @param [Symbol] opts
|
16
|
+
def valid_options(*opts)
|
17
|
+
Array(opts).each do |opt|
|
18
|
+
CookbookSource.add_valid_option(opt)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns an array where the first element is string representing the best version
|
23
|
+
# for the given constraint and the second element is the URI to where the corresponding
|
24
|
+
# version of the Cookbook can be downloaded from
|
25
|
+
#
|
26
|
+
# @example:
|
27
|
+
# constraint = Solve::Constraint.new("~> 0.101.2")
|
28
|
+
# versions = {
|
29
|
+
# "1.0.0" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/1_0_0",
|
30
|
+
# "2.0.0" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/2_0_0"
|
31
|
+
# }
|
32
|
+
#
|
33
|
+
# subject.solve_for_constraint(versions, constraint) =>
|
34
|
+
# [ "2.0.0", "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/2_0_0" ]
|
35
|
+
#
|
36
|
+
# @param [String, Solve::Constraint] constraint
|
37
|
+
# version constraint to solve for
|
38
|
+
#
|
39
|
+
# @param [Hash] versions
|
40
|
+
# a hash where the keys are a string representing a cookbook version and the values
|
41
|
+
# are the download URL for the cookbook version.
|
42
|
+
#
|
43
|
+
# @return [Array, nil]
|
44
|
+
def solve_for_constraint(constraint, versions)
|
45
|
+
graph = Solve::Graph.new
|
46
|
+
name = "none"
|
47
|
+
|
48
|
+
versions.each do |version, uri|
|
49
|
+
graph.artifacts(name, version)
|
50
|
+
end
|
51
|
+
|
52
|
+
graph.demands(name, constraint)
|
53
|
+
result = Solve.it(graph)
|
54
|
+
|
55
|
+
return nil if result.nil?
|
56
|
+
|
57
|
+
version = result[name]
|
58
|
+
|
59
|
+
[ version, versions[version] ]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class << self
|
64
|
+
def included(base)
|
65
|
+
base.send :extend, ClassMethods
|
66
|
+
end
|
67
|
+
|
68
|
+
# Creates a new instance of a Class implementing Location with the given name and
|
69
|
+
# constraint. Which Class to instantiated is determined by the values in the given
|
70
|
+
# options Hash. Source Locations have an associated location_key registered with
|
71
|
+
# CookbookSource. If your options Hash contains a key matching one of these location_keys
|
72
|
+
# then the Class who registered that location_key will be instantiated. If you do not
|
73
|
+
# provide an option with a matching location_key a SiteLocation class will be
|
74
|
+
# instantiated.
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# Location.init("nginx", ">= 0.0.0", git: "git://github.com/RiotGames/artifact-cookbook.git") =>
|
78
|
+
# instantiates a GitLocation
|
79
|
+
#
|
80
|
+
# Location.init("nginx", ">= 0.0.0", path: "/Users/reset/code/nginx-cookbook") =>
|
81
|
+
# instantiates a PathLocation
|
82
|
+
#
|
83
|
+
# Location.init("nginx", ">= 0.0.0", site: "http://cookbooks.opscode.com/api/v1/cookbooks") =>
|
84
|
+
# instantiates a SiteLocation
|
85
|
+
#
|
86
|
+
# Location.init("nginx", ">= 0.0.0", chef_api: "https://api.opscode.com/organizations/vialstudios") =>
|
87
|
+
# instantiates a ChefAPILocation
|
88
|
+
#
|
89
|
+
# Location.init("nginx", ">= 0.0.0") =>
|
90
|
+
# instantiates a SiteLocation
|
91
|
+
#
|
92
|
+
# @param [String] name
|
93
|
+
# @param [String, Solve::Constraint] constraint
|
94
|
+
# @param [Hash] options
|
95
|
+
#
|
96
|
+
# @return [SiteLocation, PathLocation, GitLocation, ChefAPILocation]
|
97
|
+
def init(name, constraint, options = {})
|
98
|
+
klass = klass_from_options(options)
|
99
|
+
|
100
|
+
klass.new(name, constraint, options)
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def klass_from_options(options)
|
106
|
+
location_keys = (options.keys & CookbookSource.location_keys.keys)
|
107
|
+
if location_keys.length > 1
|
108
|
+
location_keys.collect! { |opt| "'#{opt}'" }
|
109
|
+
raise InternalError, "Only one location key (#{CookbookSource.location_keys.keys.join(', ')}) may be specified. You gave #{location_keys.join(', ')}."
|
110
|
+
end
|
111
|
+
|
112
|
+
if location_keys.empty?
|
113
|
+
SiteLocation
|
114
|
+
else
|
115
|
+
CookbookSource.location_keys[location_keys.first]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
5
120
|
attr_reader :name
|
6
121
|
attr_reader :version_constraint
|
7
122
|
|
8
123
|
# @param [#to_s] name
|
9
|
-
|
124
|
+
# @param [Solve::Constraint] version_constraint
|
125
|
+
# @param [Hash] options
|
126
|
+
def initialize(name, version_constraint, options = {})
|
10
127
|
@name = name
|
11
128
|
@version_constraint = version_constraint
|
12
129
|
@downloaded_status = false
|
@@ -33,7 +150,7 @@ module Berkshelf
|
|
33
150
|
#
|
34
151
|
# @return [Boolean]
|
35
152
|
def validate_cached(cached_cookbook)
|
36
|
-
unless version_constraint.
|
153
|
+
unless version_constraint.satisfies?(cached_cookbook.version)
|
37
154
|
raise ConstraintNotSatisfied, "A cookbook satisfying '#{name}' (#{version_constraint}) not found at #{self}"
|
38
155
|
end
|
39
156
|
|
@@ -4,11 +4,16 @@ module Berkshelf
|
|
4
4
|
class PathLocation
|
5
5
|
include Location
|
6
6
|
|
7
|
+
location_key :path
|
8
|
+
|
7
9
|
attr_accessor :path
|
8
10
|
|
9
11
|
# @param [#to_s] name
|
10
|
-
# @param [
|
12
|
+
# @param [Solve::Constraint] version_constraint
|
11
13
|
# @param [Hash] options
|
14
|
+
#
|
15
|
+
# @option options [String] :path
|
16
|
+
# a filepath to the cookbook on your local disk
|
12
17
|
def initialize(name, version_constraint, options = {})
|
13
18
|
@name = name
|
14
19
|
@version_constraint = version_constraint
|
@@ -4,6 +4,8 @@ module Berkshelf
|
|
4
4
|
class SiteLocation
|
5
5
|
include Location
|
6
6
|
|
7
|
+
location_key :site
|
8
|
+
|
7
9
|
attr_reader :api_uri
|
8
10
|
attr_accessor :version_constraint
|
9
11
|
|
@@ -17,51 +19,21 @@ module Berkshelf
|
|
17
19
|
def unpack(target, destination)
|
18
20
|
Archive::Tar::Minitar.unpack(Zlib::GzipReader.new(File.open(target, 'rb')), destination)
|
19
21
|
end
|
20
|
-
|
21
|
-
# @param [DepSelector::VersionConstraint] constraint
|
22
|
-
# version constraint to solve for
|
23
|
-
#
|
24
|
-
# @param [Hash] versions
|
25
|
-
# a hash where the keys are a DepSelector::Version representing a Cookbook version
|
26
|
-
# number and their values are the URI the Cookbook of the corrosponding version can
|
27
|
-
# be downloaded from. This format is also the output of the #versions function on
|
28
|
-
# instances of this class.
|
29
|
-
#
|
30
|
-
# Example:
|
31
|
-
# {
|
32
|
-
# 0.101.2 => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_2",
|
33
|
-
# 0.101.0 => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_0",
|
34
|
-
# 0.100.2 => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_100_2",
|
35
|
-
# 0.100.0 => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_100_0"
|
36
|
-
# }
|
37
|
-
#
|
38
|
-
# @return [Array, nil]
|
39
|
-
# an array where the first element is a DepSelector::Version representing the best version
|
40
|
-
# for the given constraint and the second element is the URI to where the corrosponding
|
41
|
-
# version of the Cookbook can be downloaded from
|
42
|
-
#
|
43
|
-
# Example:
|
44
|
-
# [ 0.101.2 => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_2" ]
|
45
|
-
def solve_for_constraint(constraint, versions)
|
46
|
-
versions.each do |version, uri|
|
47
|
-
if constraint.include?(version)
|
48
|
-
return [ version, uri ]
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
nil
|
53
|
-
end
|
54
22
|
end
|
55
23
|
|
56
24
|
# @param [#to_s] name
|
57
|
-
# @param [
|
25
|
+
# @param [Solve::Constraint] version_constraint
|
58
26
|
# @param [Hash] options
|
27
|
+
#
|
28
|
+
# @option options [String] :site
|
29
|
+
# a URL pointing to a community API endpoint
|
59
30
|
def initialize(name, version_constraint, options = {})
|
60
31
|
options[:site] ||= OPSCODE_COMMUNITY_API
|
61
32
|
|
62
33
|
@name = name
|
63
34
|
@version_constraint = version_constraint
|
64
35
|
@api_uri = options[:site]
|
36
|
+
@rest = Chef::REST.new(api_uri, false, false)
|
65
37
|
end
|
66
38
|
|
67
39
|
# @param [#to_s] destination
|
@@ -70,6 +42,7 @@ module Berkshelf
|
|
70
42
|
def download(destination)
|
71
43
|
version, uri = target_version
|
72
44
|
remote_file = rest.get_rest(uri)['file']
|
45
|
+
|
73
46
|
downloaded_tf = rest.get_rest(remote_file, true)
|
74
47
|
|
75
48
|
dir = Dir.mktmpdir
|
@@ -85,52 +58,26 @@ module Berkshelf
|
|
85
58
|
cached
|
86
59
|
end
|
87
60
|
|
88
|
-
#
|
89
|
-
#
|
90
|
-
# the Cookbook and the second element is the URI to where the corrosponding version of the
|
91
|
-
# Cookbook can be downloaded from
|
61
|
+
# Returns a hash of all the cookbook versions found at communite site URL for the cookbook
|
62
|
+
# name of this location.
|
92
63
|
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
dep_ver = DepSelector::Version.new(result['version'])
|
99
|
-
|
100
|
-
[ dep_ver, result['file'] ]
|
101
|
-
}
|
102
|
-
rescue Net::HTTPServerException => e
|
103
|
-
if e.response.code == "404"
|
104
|
-
raise CookbookNotFound, "Cookbook name: '#{name}' version: '#{version_string}' not found at site: '#{api_uri}'"
|
105
|
-
else
|
106
|
-
raise
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
# @return [Hash]
|
111
|
-
# a hash where the keys are a DepSelector::Version representing a Cookbook version
|
112
|
-
# number and their values are the URI the Cookbook of the corrosponding version can
|
113
|
-
# be downloaded from
|
64
|
+
# @example
|
65
|
+
# {
|
66
|
+
# "0.101.2" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_2",
|
67
|
+
# "0.101.0" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_0"
|
68
|
+
# }
|
114
69
|
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
# 0.101.0 => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_0",
|
119
|
-
# 0.100.2 => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_100_2",
|
120
|
-
# 0.100.0 => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_100_0"
|
121
|
-
# }
|
70
|
+
# @return [Hash]
|
71
|
+
# a hash representing the cookbook versions on at a Chef API for location's cookbook.
|
72
|
+
# The keys are version strings and the values are URLs to download the cookbook version.
|
122
73
|
def versions
|
123
|
-
|
124
|
-
quietly {
|
74
|
+
{}.tap do |versions|
|
125
75
|
rest.get_rest(name)['versions'].each do |uri|
|
126
|
-
|
127
|
-
version = DepSelector::Version.new(version_string)
|
76
|
+
version = version_from_uri(File.basename(uri))
|
128
77
|
|
129
78
|
versions[version] = uri
|
130
79
|
end
|
131
|
-
|
132
|
-
|
133
|
-
versions
|
80
|
+
end
|
134
81
|
rescue Net::HTTPServerException => e
|
135
82
|
if e.response.code == "404"
|
136
83
|
raise CookbookNotFound, "Cookbook '#{name}' not found at site: '#{api_uri}'"
|
@@ -139,20 +86,19 @@ module Berkshelf
|
|
139
86
|
end
|
140
87
|
end
|
141
88
|
|
142
|
-
#
|
143
|
-
# an array where the first element is a DepSelector::Version representing the latest version of
|
144
|
-
# the Cookbook and the second element is the URI to where the corrosponding version of the
|
145
|
-
# Cookbook can be downloaded from
|
89
|
+
# Returns the latest version of the cookbook and it's download link.
|
146
90
|
#
|
147
|
-
#
|
148
|
-
#
|
91
|
+
# @example
|
92
|
+
# [ "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2" ]
|
93
|
+
#
|
94
|
+
# @return [Array]
|
95
|
+
# an array containing the version and download URL for the cookbook version that
|
96
|
+
# should be downloaded for this location.
|
149
97
|
def latest_version
|
150
98
|
quietly {
|
151
99
|
uri = rest.get_rest(name)['latest_version']
|
152
|
-
version_string = version_from_uri(uri)
|
153
|
-
dep_ver = DepSelector::Version.new(version_string)
|
154
100
|
|
155
|
-
[
|
101
|
+
[ version_from_uri(uri), uri ]
|
156
102
|
}
|
157
103
|
rescue Net::HTTPServerException => e
|
158
104
|
if e.response.code == "404"
|
@@ -162,20 +108,13 @@ module Berkshelf
|
|
162
108
|
end
|
163
109
|
end
|
164
110
|
|
165
|
-
def api_uri=(uri)
|
166
|
-
@rest = nil
|
167
|
-
@api_uri = uri
|
168
|
-
end
|
169
|
-
|
170
111
|
def to_s
|
171
112
|
"site: '#{api_uri}'"
|
172
113
|
end
|
173
114
|
|
174
115
|
private
|
175
116
|
|
176
|
-
|
177
|
-
@rest ||= Chef::REST.new(api_uri, false, false)
|
178
|
-
end
|
117
|
+
attr_reader :rest
|
179
118
|
|
180
119
|
def uri_escape_version(version)
|
181
120
|
version.gsub('.', '_')
|
@@ -185,27 +124,19 @@ module Berkshelf
|
|
185
124
|
File.basename(latest_version).gsub('_', '.')
|
186
125
|
end
|
187
126
|
|
188
|
-
#
|
189
|
-
#
|
190
|
-
# the URI to where the corrosponding version of the Cookbook can be downloaded from.
|
191
|
-
#
|
192
|
-
#
|
193
|
-
# The version is determined by the value of the version_constraint attribute of this
|
194
|
-
# instance of SiteLocation:
|
127
|
+
# Returns an array containing the version and download URL for the cookbook version that
|
128
|
+
# should be downloaded for this location.
|
195
129
|
#
|
196
|
-
#
|
130
|
+
# @example
|
131
|
+
# [ "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2" ]
|
197
132
|
#
|
198
|
-
#
|
199
|
-
# available versions will be solved
|
200
|
-
#
|
201
|
-
# Example:
|
202
|
-
# [ 0.101.2, "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_2" ]
|
133
|
+
# @return [Array]
|
203
134
|
def target_version
|
204
135
|
if version_constraint
|
205
136
|
solution = self.class.solve_for_constraint(version_constraint, versions)
|
206
137
|
|
207
138
|
unless solution
|
208
|
-
raise NoSolution, "No cookbook version of '#{name}' found at
|
139
|
+
raise NoSolution, "No cookbook version of '#{name}' found at #{self} that would satisfy constraint (#{version_constraint})."
|
209
140
|
end
|
210
141
|
|
211
142
|
solution
|