berkshelf 0.3.7 → 0.4.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|