librarian-chef-nochef 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,481 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'digest'
7
+ require 'zlib'
8
+ require 'securerandom'
9
+ require 'archive/tar/minitar'
10
+
11
+ require 'librarian/source/basic_api'
12
+ require 'librarian/chef/manifest_reader'
13
+
14
+ module Librarian
15
+ module Chef
16
+ module Source
17
+ class Site
18
+
19
+ class Line
20
+
21
+ attr_accessor :source, :name
22
+ private :source=, :name=
23
+
24
+ def initialize(source, name)
25
+ self.source = source
26
+ self.name = name
27
+ end
28
+
29
+ def install_version!(version, install_path)
30
+ cache_version_unpacked! version
31
+
32
+ if install_path.exist?
33
+ debug { "Deleting #{relative_path_to(install_path)}" }
34
+ install_path.rmtree
35
+ end
36
+
37
+ unpacked_path = version_unpacked_cache_path(version)
38
+
39
+ debug { "Copying #{relative_path_to(unpacked_path)} to #{relative_path_to(install_path)}" }
40
+ FileUtils.cp_r(unpacked_path, install_path)
41
+ end
42
+
43
+ def manifests
44
+ version_uris.map do |version_uri|
45
+ Manifest.new(source, name, version_uri)
46
+ end
47
+ end
48
+
49
+ def to_version(version_uri)
50
+ version_uri_metadata(version_uri)["version"]
51
+ end
52
+
53
+ def version_dependencies(version)
54
+ version_manifest(version)["dependencies"]
55
+ end
56
+
57
+ private
58
+
59
+ attr_accessor :metadata_cached
60
+ alias metadata_cached? metadata_cached
61
+
62
+ def environment
63
+ source.environment
64
+ end
65
+
66
+ def uri
67
+ @uri ||= URI.parse("#{source.uri}/cookbooks/#{name}")
68
+ end
69
+
70
+ def version_uris
71
+ metadata["versions"]
72
+ end
73
+
74
+ def version_metadata(version)
75
+ version_uri = to_version_uri(version)
76
+ version_uri_metadata(version_uri)
77
+ end
78
+
79
+ def version_uri_metadata(version_uri)
80
+ memo(__method__, version_uri.to_s) do
81
+ cache_version_uri_metadata! version_uri
82
+ parse_local_json(version_uri_metadata_cache_path(version_uri))
83
+ end
84
+ end
85
+
86
+ def version_manifest(version)
87
+ version_uri = to_version_uri(version)
88
+ version_uri_manifest(version_uri)
89
+ end
90
+
91
+ def version_uri_manifest(version_uri)
92
+ memo(__method__, version_uri.to_s) do
93
+ cache_version_uri_unpacked! version_uri
94
+ unpacked_path = version_uri_unpacked_cache_path(version_uri)
95
+ manifest_path = ManifestReader.manifest_path(unpacked_path)
96
+ ManifestReader.read_manifest(name, manifest_path)
97
+ end
98
+ end
99
+
100
+ def metadata
101
+ @metadata ||= begin
102
+ cache_metadata!
103
+ parse_local_json(metadata_cache_path)
104
+ end
105
+ end
106
+
107
+ def to_version_uri(version)
108
+ memo(__method__, version.to_s) do
109
+ cache_version! version
110
+ version_cache_path(version).read
111
+ end
112
+ end
113
+
114
+ def metadata_cached!
115
+ self.metadata_cached = true
116
+ end
117
+
118
+ def cache_path
119
+ @cache_path ||= source.cache_path.join(name)
120
+ end
121
+
122
+ def metadata_cache_path
123
+ @metadata_cache_path ||= cache_path.join("metadata.json")
124
+ end
125
+
126
+ def version_cache_path(version)
127
+ memo(__method__, version.to_s) do
128
+ cache_path.join("version").join(version.to_s)
129
+ end
130
+ end
131
+
132
+ def version_uri_cache_path(version_uri)
133
+ memo(__method__, version_uri.to_s) do
134
+ cache_path.join("version-uri").join(hexdigest(version_uri))
135
+ end
136
+ end
137
+
138
+ def version_metadata_cache_path(version)
139
+ version_uri = to_version_uri(version)
140
+ version_uri_metadata_cache_path(version_uri)
141
+ end
142
+
143
+ def version_uri_metadata_cache_path(version_uri)
144
+ memo(__method__, version_uri.to_s) do
145
+ version_uri_cache_path(version_uri).join("metadata.json")
146
+ end
147
+ end
148
+
149
+ def version_package_cache_path(version)
150
+ version_uri = to_version_uri(version)
151
+ version_uri_package_cache_path(version_uri)
152
+ end
153
+
154
+ def version_uri_package_cache_path(version_uri)
155
+ memo(__method__, version_uri.to_s) do
156
+ version_uri_cache_path(version_uri).join("package.tar.gz")
157
+ end
158
+ end
159
+
160
+ def version_unpacked_cache_path(version)
161
+ version_uri = to_version_uri(version)
162
+ version_uri_unpacked_cache_path(version_uri)
163
+ end
164
+
165
+ def version_uri_unpacked_cache_path(version_uri)
166
+ memo(__method__, version_uri.to_s) do
167
+ version_uri_cache_path(version_uri).join("package")
168
+ end
169
+ end
170
+
171
+ def cache_metadata!
172
+ metadata_cached? and return or metadata_cached!
173
+ cache_remote_json! metadata_cache_path, uri
174
+ end
175
+
176
+ def cache_version_uri_metadata!(version_uri)
177
+ path = version_uri_metadata_cache_path(version_uri)
178
+ path.file? and return
179
+
180
+ cache_remote_json! path, version_uri
181
+ end
182
+
183
+ def cache_version!(version)
184
+ path = version_cache_path(version)
185
+ path.file? and return
186
+
187
+ version_uris.each do |version_uri|
188
+ m = version_uri_metadata(version_uri)
189
+ v = m["version"]
190
+ if version.to_s == v
191
+ write! path, version_uri.to_s
192
+ break
193
+ end
194
+ end
195
+ end
196
+
197
+ def cache_version_package!(version)
198
+ version_uri = to_version_uri(version)
199
+ cache_version_uri_package! version_uri
200
+ end
201
+
202
+ def cache_version_uri_package!(version_uri)
203
+ path = version_uri_package_cache_path(version_uri)
204
+ path.file? and return
205
+
206
+ file_uri = version_uri_metadata(version_uri)["file"]
207
+ cache_remote_object! path, file_uri
208
+ end
209
+
210
+ def cache_version_unpacked!(version)
211
+ version_uri = to_version_uri(version)
212
+ cache_version_uri_unpacked! version_uri
213
+ end
214
+
215
+ def cache_version_uri_unpacked!(version_uri)
216
+ cache_version_uri_package!(version_uri)
217
+
218
+ path = version_uri_unpacked_cache_path(version_uri)
219
+ path.directory? and return
220
+
221
+ package_path = version_uri_package_cache_path(version_uri)
222
+ unpacked_path = version_uri_unpacked_cache_path(version_uri)
223
+
224
+ unpack_package! unpacked_path, package_path
225
+ end
226
+
227
+ def cache_remote_json!(path, uri)
228
+ cache_remote_object!(path, uri, :type => :json)
229
+ end
230
+
231
+ def cache_remote_object!(path, uri, options = { })
232
+ path = Pathname(path)
233
+ uri = to_uri(uri)
234
+ type = options[:type]
235
+
236
+ debug { "Caching #{uri} to #{path}" }
237
+
238
+ response = http_get(uri)
239
+
240
+ object = response.body
241
+ case type
242
+ when :json
243
+ JSON.parse(object) # verify that it's really JSON.
244
+ end
245
+ write! path, object
246
+ end
247
+
248
+ def write!(path, bytes)
249
+ path.dirname.mkpath
250
+ path.open("wb"){|f| f.write(bytes)}
251
+ end
252
+
253
+ def path_detect_gzip?(path)
254
+ Zlib::GzipReader.open(path) { true }
255
+ rescue Zlib::GzipFile::Error
256
+ false
257
+ end
258
+
259
+ def path_detect_tar?(path)
260
+ path_read_bytes_at(path, 257, 8) == "ustar\x0000"
261
+ end
262
+
263
+ def path_read_bytes_at(path, pos, len)
264
+ path = Pathname(path)
265
+ path.stat.size >= pos + len or return
266
+ path.open "rb" do |f|
267
+ f.seek pos ; f.pos == pos or return
268
+ f.read(len)
269
+ end
270
+ end
271
+
272
+ def extract_archive!(source, destination)
273
+ source = Pathname(source)
274
+ destination = Pathname(destination)
275
+
276
+ return extract_archive_targz! source, destination if path_detect_gzip?(source)
277
+ return extract_archive_tar! source, destination if path_detect_tar?(source)
278
+ raise "Unrecognized archive format!"
279
+ end
280
+
281
+ def extract_archive_targz!(source, destination)
282
+ Zlib::GzipReader.open(source) do |input|
283
+ Archive::Tar::Minitar.unpack(input, destination.to_s)
284
+ end
285
+ end
286
+
287
+ def extract_archive_tar!(source, destination)
288
+ source.open "rb" do |input|
289
+ Archive::Tar::Minitar.unpack(input, destination.to_s)
290
+ end
291
+ end
292
+
293
+ def unpack_package!(path, source)
294
+ path = Pathname(path)
295
+ source = Pathname(source)
296
+
297
+ temp = environment.scratch_path.join(SecureRandom.hex(16))
298
+ temp.mkpath
299
+
300
+ debug { "Unpacking #{relative_path_to(source)} to #{relative_path_to(temp)}" }
301
+ extract_archive! source, temp
302
+
303
+ # Cookbook files, as pulled from Opscode Community Site API, are
304
+ # embedded in a subdirectory of the tarball. If created by git archive they
305
+ # can include the subfolder `pax_global_header`, which is ignored.
306
+ subtemps = temp.children
307
+ subtemps.empty? and raise "The package archive was empty!"
308
+ subtemps.delete_if{|pth| pth.to_s[/pax_global_header/]}
309
+ subtemps.size > 1 and raise "The package archive has too many children!"
310
+ subtemp = subtemps.first
311
+ debug { "Moving #{relative_path_to(subtemp)} to #{relative_path_to(path)}" }
312
+ FileUtils.cp_r(subtemp, path)
313
+ FileUtils.rm_rf(subtemp)
314
+ ensure
315
+ temp.rmtree if temp && temp.exist?
316
+ end
317
+
318
+ def parse_local_json(path)
319
+ JSON.parse(path.read)
320
+ end
321
+
322
+ def hexdigest(bytes)
323
+ Digest::MD5.hexdigest(bytes)[0..15]
324
+ end
325
+
326
+ def to_uri(uri)
327
+ uri = URI(uri) unless URI === uri
328
+ uri
329
+ end
330
+
331
+ def debug(*args, &block)
332
+ environment.logger.debug(*args, &block)
333
+ end
334
+
335
+ def relative_path_to(path)
336
+ environment.logger.relative_path_to(path)
337
+ end
338
+
339
+ def http(uri)
340
+ environment.net_http_class(uri.host).new(uri.host, uri.port)
341
+ end
342
+
343
+ def http_get(uri)
344
+ max_redirects = 10
345
+ redirects = []
346
+
347
+ loop do
348
+ debug { "Performing http-get for #{uri}" }
349
+ http = http(uri)
350
+ request = Net::HTTP::Get.new(uri.path)
351
+ response = http.start{|http| http.request(request)}
352
+
353
+ case response
354
+ when Net::HTTPSuccess
355
+ debug { "Responded with success" }
356
+ return response
357
+ when Net::HTTPRedirection
358
+ location = response["Location"]
359
+ debug { "Responded with redirect to #{uri}" }
360
+ redirects.size > max_redirects and raise Error,
361
+ "Could not get #{uri} because too many redirects!"
362
+ redirects.include?(location) and raise Error,
363
+ "Could not get #{uri} because redirect cycle!"
364
+ redirects << location
365
+ uri = URI.parse(location)
366
+ # continue the loop
367
+ else
368
+ raise Error, "Could not get #{uri} because #{response.code} #{response.message}!"
369
+ end
370
+ end
371
+ end
372
+
373
+ def memo(method, *path)
374
+ ivar = "@#{method}".to_sym
375
+ unless memo = instance_variable_get(ivar)
376
+ memo = instance_variable_set(ivar, { })
377
+ end
378
+
379
+ memo.key?(path) or memo[path] = yield
380
+ memo[path]
381
+ end
382
+
383
+ end
384
+
385
+ include Librarian::Source::BasicApi
386
+
387
+ lock_name 'SITE'
388
+ spec_options []
389
+
390
+ attr_accessor :environment, :uri
391
+ private :environment=, :uri=
392
+
393
+ def initialize(environment, uri, options = {})
394
+ self.environment = environment
395
+ self.uri = uri
396
+ end
397
+
398
+ def to_s
399
+ uri
400
+ end
401
+
402
+ def ==(other)
403
+ other &&
404
+ self.class == other.class &&
405
+ self.uri == other.uri
406
+ end
407
+
408
+ def to_spec_args
409
+ [uri, {}]
410
+ end
411
+
412
+ def to_lock_options
413
+ {:remote => uri}
414
+ end
415
+
416
+ def pinned?
417
+ false
418
+ end
419
+
420
+ def unpin!
421
+ end
422
+
423
+ def install!(manifest)
424
+ manifest.source == self or raise ArgumentError
425
+
426
+ name = manifest.name
427
+ version = manifest.version
428
+ install_path = install_path(name)
429
+ line = line(name)
430
+
431
+ info { "Installing #{manifest.name} (#{manifest.version})" }
432
+
433
+ debug { "Installing #{manifest}" }
434
+
435
+ line.install_version! version, install_path
436
+ end
437
+
438
+ # NOTE:
439
+ # Assumes the Opscode Site API responds with versions in reverse sorted order
440
+ def manifests(name)
441
+ line(name).manifests
442
+ end
443
+
444
+ def cache_path
445
+ @cache_path ||= begin
446
+ dir = Digest::MD5.hexdigest(uri)[0..15]
447
+ environment.cache_path.join("source/chef/site/#{dir}")
448
+ end
449
+ end
450
+
451
+ def install_path(name)
452
+ environment.install_path.join(name)
453
+ end
454
+
455
+ def fetch_version(name, version_uri)
456
+ line(name).to_version(version_uri)
457
+ end
458
+
459
+ def fetch_dependencies(name, version, version_uri)
460
+ line(name).version_dependencies(version).map{|k, v| Dependency.new(k, v, nil)}
461
+ end
462
+
463
+ private
464
+
465
+ def line(name)
466
+ @line ||= { }
467
+ @line[name] ||= Line.new(self, name)
468
+ end
469
+
470
+ def info(*args, &block)
471
+ environment.logger.info(*args, &block)
472
+ end
473
+
474
+ def debug(*args, &block)
475
+ environment.logger.debug(*args, &block)
476
+ end
477
+
478
+ end
479
+ end
480
+ end
481
+ end