librarian-chef 0.0.1.beta.1

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.
@@ -0,0 +1 @@
1
+ require "librarian/chef"
@@ -0,0 +1 @@
1
+ require 'librarian/chef/extension'
@@ -0,0 +1,47 @@
1
+ require 'librarian/helpers'
2
+
3
+ require 'librarian/cli'
4
+ require 'librarian/chef'
5
+
6
+ module Librarian
7
+ module Chef
8
+ class Cli < Librarian::Cli
9
+
10
+ module Particularity
11
+ def root_module
12
+ Chef
13
+ end
14
+ end
15
+
16
+ extend Particularity
17
+
18
+ source_root Pathname.new(__FILE__).dirname.join("templates")
19
+
20
+ def init
21
+ copy_file environment.specfile_name
22
+ end
23
+
24
+ desc "install", "Resolves and installs all of the dependencies you specify."
25
+ option "quiet", :type => :boolean, :default => false
26
+ option "verbose", :type => :boolean, :default => false
27
+ option "line-numbers", :type => :boolean, :default => false
28
+ option "clean", :type => :boolean, :default => false
29
+ option "strip-dot-git", :type => :boolean
30
+ option "path", :type => :string
31
+ def install
32
+ ensure!
33
+ clean! if options["clean"]
34
+ if options.include?("strip-dot-git")
35
+ strip_dot_git_val = options["strip-dot-git"] ? "1" : nil
36
+ environment.config_db.local["install.strip-dot-git"] = strip_dot_git_val
37
+ end
38
+ if options.include?("path")
39
+ environment.config_db.local["path"] = options["path"]
40
+ end
41
+ resolve!
42
+ install!
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ require 'librarian/dsl'
2
+ require 'librarian/chef/source'
3
+
4
+ module Librarian
5
+ module Chef
6
+ class Dsl < Librarian::Dsl
7
+
8
+ dependency :cookbook
9
+
10
+ source :site => Source::Site
11
+ source :git => Source::Git
12
+ source :github => Source::Github
13
+ source :path => Source::Path
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ require "librarian/environment"
2
+ require "librarian/chef/dsl"
3
+ require "librarian/chef/source"
4
+ require "librarian/chef/version"
5
+
6
+ module Librarian
7
+ module Chef
8
+ class Environment < Environment
9
+
10
+ def adapter_name
11
+ "chef"
12
+ end
13
+
14
+ def adapter_version
15
+ VERSION
16
+ end
17
+
18
+ def install_path
19
+ part = config_db["path"] || "cookbooks"
20
+ project_path.join(part)
21
+ end
22
+
23
+ def config_keys
24
+ super + %w[
25
+ install.strip-dot-git
26
+ path
27
+ ]
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ require 'librarian/chef/environment'
2
+
3
+ module Librarian
4
+ module Chef
5
+ extend self
6
+ extend Librarian
7
+
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ require 'pathname'
2
+ require 'securerandom'
3
+ require 'highline'
4
+
5
+ require 'librarian'
6
+ require 'librarian/action/install'
7
+ require 'librarian/chef'
8
+
9
+ module Librarian
10
+ module Chef
11
+
12
+ class Environment
13
+ def install_path
14
+ @install_path ||= begin
15
+ has_home = ENV["HOME"] && File.directory?(ENV["HOME"])
16
+ tmp_dir = Pathname.new(has_home ? "~/.librarian/tmp" : "/tmp/librarian").expand_path
17
+ enclosing = tmp_dir.join("chef/integration/knife/install")
18
+ enclosing.mkpath unless enclosing.exist?
19
+ dir = enclosing.join(SecureRandom.hex(16))
20
+ dir.mkpath
21
+ at_exit { dir.rmtree }
22
+ dir
23
+ end
24
+ end
25
+ end
26
+
27
+ def environment
28
+ @environment ||= environment_class.new
29
+ end
30
+
31
+ def install_path
32
+ environment.install_path
33
+ end
34
+
35
+ hl = HighLine.new
36
+
37
+ begin
38
+ Action::Install.new(environment).run
39
+ rescue Error => e
40
+ message = hl.color(e.message, HighLine::RED)
41
+ hl.say(message)
42
+ Process.exit!(1)
43
+ end
44
+
45
+ end
46
+ end
@@ -0,0 +1,59 @@
1
+ require 'json'
2
+ require 'yaml'
3
+
4
+ require 'librarian/manifest'
5
+
6
+ module Librarian
7
+ module Chef
8
+ module ManifestReader
9
+ extend self
10
+
11
+ MANIFESTS = %w(metadata.json metadata.yml metadata.yaml metadata.rb)
12
+
13
+ def manifest_path(path)
14
+ MANIFESTS.map{|s| path.join(s)}.find{|s| s.exist?}
15
+ end
16
+
17
+ def read_manifest(name, manifest_path)
18
+ case manifest_path.extname
19
+ when ".json" then JSON.parse(binread(manifest_path))
20
+ when ".yml", ".yaml" then YAML.load(binread(manifest_path))
21
+ when ".rb" then compile_manifest(name, manifest_path.dirname)
22
+ end
23
+ end
24
+
25
+ def compile_manifest(name, path)
26
+ # Inefficient, if there are many cookbooks with uncompiled metadata.
27
+ require 'chef/json_compat'
28
+ require 'chef/cookbook/metadata'
29
+ md = ::Chef::Cookbook::Metadata.new
30
+ md.name(name)
31
+ md.from_file(path.join('metadata.rb').to_s)
32
+ {"name" => md.name, "version" => md.version, "dependencies" => md.dependencies}
33
+ end
34
+
35
+ def manifest?(name, path)
36
+ path = Pathname.new(path)
37
+ !!manifest_path(path)
38
+ end
39
+
40
+ def check_manifest(name, manifest_path)
41
+ manifest = read_manifest(name, manifest_path)
42
+ manifest["name"] == name
43
+ end
44
+
45
+ private
46
+
47
+ if IO.respond_to?(:binread)
48
+ def binread(path)
49
+ path.binread
50
+ end
51
+ else
52
+ def binread(path)
53
+ path.read
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,4 @@
1
+ require 'librarian/chef/source/path'
2
+ require 'librarian/chef/source/git'
3
+ require 'librarian/chef/source/github'
4
+ require 'librarian/chef/source/site'
@@ -0,0 +1,25 @@
1
+ require 'librarian/source/git'
2
+ require 'librarian/chef/source/local'
3
+
4
+ module Librarian
5
+ module Chef
6
+ module Source
7
+ class Git < Librarian::Source::Git
8
+ include Local
9
+
10
+ private
11
+
12
+ def install_perform_step_copy!(found_path, install_path)
13
+ debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" }
14
+ FileUtils.cp_r(found_path, install_path)
15
+
16
+ if environment.config_db["install.strip-dot-git"] == "1"
17
+ dot_git = install_path.join(".git")
18
+ dot_git.rmtree if dot_git.directory?
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ require 'librarian/chef/source/git'
2
+
3
+ module Librarian
4
+ module Chef
5
+ module Source
6
+ class Github
7
+
8
+ class << self
9
+
10
+ def lock_name
11
+ Git.lock_name
12
+ end
13
+
14
+ def from_lock_options(environment, options)
15
+ Git.from_lock_options(environment, options)
16
+ end
17
+
18
+ def from_spec_args(environment, uri, options)
19
+ Git.from_spec_args(environment, "https://github.com/#{uri}", options)
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,74 @@
1
+ require 'librarian/chef/manifest_reader'
2
+
3
+ module Librarian
4
+ module Chef
5
+ module Source
6
+ module Local
7
+
8
+ def install!(manifest)
9
+ manifest.source == self or raise ArgumentError
10
+
11
+ info { "Installing #{manifest.name} (#{manifest.version})" }
12
+
13
+ debug { "Installing #{manifest}" }
14
+
15
+ name, version = manifest.name, manifest.version
16
+ found_path = found_path(name)
17
+
18
+ install_path = environment.install_path.join(name)
19
+ if install_path.exist?
20
+ debug { "Deleting #{relative_path_to(install_path)}" }
21
+ install_path.rmtree
22
+ end
23
+
24
+ install_perform_step_copy!(found_path, install_path)
25
+ end
26
+
27
+ def fetch_version(name, extra)
28
+ manifest_data(name)["version"]
29
+ end
30
+
31
+ def fetch_dependencies(name, version, extra)
32
+ manifest_data(name)["dependencies"]
33
+ end
34
+
35
+ private
36
+
37
+ def install_perform_step_copy!(found_path, install_path)
38
+ debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" }
39
+ FileUtils.mkdir_p(install_path)
40
+ FileUtils.cp_r(filter_path(found_path), install_path)
41
+ end
42
+
43
+ def filter_path(path)
44
+ Dir.glob("#{path}/*").reject { |e| e == environment.install_path.to_s }
45
+ end
46
+
47
+ def manifest_data(name)
48
+ @manifest_data ||= { }
49
+ @manifest_data[name] ||= fetch_manifest_data(name)
50
+ end
51
+
52
+ def fetch_manifest_data(name)
53
+ expect_manifest!(name)
54
+
55
+ found_path = found_path(name)
56
+ manifest_path = ManifestReader.manifest_path(found_path)
57
+ ManifestReader.read_manifest(name, manifest_path)
58
+ end
59
+
60
+ def manifest?(name, path)
61
+ ManifestReader.manifest?(name, path)
62
+ end
63
+
64
+ def expect_manifest!(name)
65
+ found_path = found_path(name)
66
+ return if found_path && ManifestReader.manifest_path(found_path)
67
+
68
+ raise Error, "No metadata file found for #{name} from #{self}! If this should be a cookbook, you might consider contributing a metadata file upstream or forking the cookbook to add your own metadata file."
69
+ end
70
+
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,12 @@
1
+ require 'librarian/source/path'
2
+ require 'librarian/chef/source/local'
3
+
4
+ module Librarian
5
+ module Chef
6
+ module Source
7
+ class Path < Librarian::Source::Path
8
+ include Local
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,442 @@
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 unpack_package!(path, source)
254
+ path = Pathname(path)
255
+ source = Pathname(source)
256
+
257
+ temp = environment.scratch_path.join(SecureRandom.hex(16))
258
+ temp.mkpath
259
+
260
+ debug { "Unpacking #{relative_path_to(source)} to #{relative_path_to(temp)}" }
261
+ Zlib::GzipReader.open(source) do |input|
262
+ Archive::Tar::Minitar.unpack(input, temp.to_s)
263
+ end
264
+
265
+ # Cookbook files, as pulled from Opscode Community Site API, are
266
+ # embedded in a subdirectory of the tarball. If created by git archive they
267
+ # can include the subfolder `pax_global_header`, which is ignored.
268
+ subtemps = temp.children
269
+ subtemps.empty? and raise "The package archive was empty!"
270
+ subtemps.delete_if{|pth| pth.to_s[/pax_global_header/]}
271
+ subtemps.size > 1 and raise "The package archive has too many children!"
272
+ subtemp = subtemps.first
273
+ debug { "Moving #{relative_path_to(subtemp)} to #{relative_path_to(path)}" }
274
+ FileUtils.mv(subtemp, path)
275
+ ensure
276
+ temp.rmtree if temp && temp.exist?
277
+ end
278
+
279
+ def parse_local_json(path)
280
+ JSON.parse(path.read)
281
+ end
282
+
283
+ def hexdigest(bytes)
284
+ Digest::MD5.hexdigest(bytes)[0..15]
285
+ end
286
+
287
+ def to_uri(uri)
288
+ uri = URI(uri) unless URI === uri
289
+ uri
290
+ end
291
+
292
+ def debug(*args, &block)
293
+ environment.logger.debug(*args, &block)
294
+ end
295
+
296
+ def relative_path_to(path)
297
+ environment.logger.relative_path_to(path)
298
+ end
299
+
300
+ def http(uri)
301
+ environment.net_http_class(uri.host).new(uri.host, uri.port)
302
+ end
303
+
304
+ def http_get(uri)
305
+ max_redirects = 10
306
+ redirects = []
307
+
308
+ loop do
309
+ debug { "Performing http-get for #{uri}" }
310
+ http = http(uri)
311
+ request = Net::HTTP::Get.new(uri.path)
312
+ response = http.start{|http| http.request(request)}
313
+
314
+ case response
315
+ when Net::HTTPSuccess
316
+ debug { "Responded with success" }
317
+ return response
318
+ when Net::HTTPRedirection
319
+ location = response["Location"]
320
+ debug { "Responded with redirect to #{uri}" }
321
+ redirects.size > max_redirects and raise Error,
322
+ "Could not get #{uri} because too many redirects!"
323
+ redirects.include?(location) and raise Error,
324
+ "Could not get #{uri} because redirect cycle!"
325
+ redirects << location
326
+ uri = URI.parse(location)
327
+ # continue the loop
328
+ else
329
+ raise Error, "Could not get #{uri} because #{response.code} #{response.message}!"
330
+ end
331
+ end
332
+ end
333
+
334
+ def memo(method, *path)
335
+ ivar = "@#{method}".to_sym
336
+ unless memo = instance_variable_get(ivar)
337
+ memo = instance_variable_set(ivar, { })
338
+ end
339
+
340
+ memo.key?(path) or memo[path] = yield
341
+ memo[path]
342
+ end
343
+
344
+ end
345
+
346
+ include Librarian::Source::BasicApi
347
+
348
+ lock_name 'SITE'
349
+ spec_options []
350
+
351
+ attr_accessor :environment, :uri
352
+ private :environment=, :uri=
353
+
354
+ def initialize(environment, uri, options = {})
355
+ self.environment = environment
356
+ self.uri = uri
357
+ end
358
+
359
+ def to_s
360
+ uri
361
+ end
362
+
363
+ def ==(other)
364
+ other &&
365
+ self.class == other.class &&
366
+ self.uri == other.uri
367
+ end
368
+
369
+ def to_spec_args
370
+ [uri, {}]
371
+ end
372
+
373
+ def to_lock_options
374
+ {:remote => uri}
375
+ end
376
+
377
+ def pinned?
378
+ false
379
+ end
380
+
381
+ def unpin!
382
+ end
383
+
384
+ def install!(manifest)
385
+ manifest.source == self or raise ArgumentError
386
+
387
+ name = manifest.name
388
+ version = manifest.version
389
+ install_path = install_path(name)
390
+ line = line(name)
391
+
392
+ info { "Installing #{manifest.name} (#{manifest.version})" }
393
+
394
+ debug { "Installing #{manifest}" }
395
+
396
+ line.install_version! version, install_path
397
+ end
398
+
399
+ # NOTE:
400
+ # Assumes the Opscode Site API responds with versions in reverse sorted order
401
+ def manifests(name)
402
+ line(name).manifests
403
+ end
404
+
405
+ def cache_path
406
+ @cache_path ||= begin
407
+ dir = Digest::MD5.hexdigest(uri)[0..15]
408
+ environment.cache_path.join("source/chef/site/#{dir}")
409
+ end
410
+ end
411
+
412
+ def install_path(name)
413
+ environment.install_path.join(name)
414
+ end
415
+
416
+ def fetch_version(name, version_uri)
417
+ line(name).to_version(version_uri)
418
+ end
419
+
420
+ def fetch_dependencies(name, version, version_uri)
421
+ line(name).version_dependencies(version).map{|k, v| Dependency.new(k, v, nil)}
422
+ end
423
+
424
+ private
425
+
426
+ def line(name)
427
+ @line ||= { }
428
+ @line[name] ||= Line.new(self, name)
429
+ end
430
+
431
+ def info(*args, &block)
432
+ environment.logger.info(*args, &block)
433
+ end
434
+
435
+ def debug(*args, &block)
436
+ environment.logger.debug(*args, &block)
437
+ end
438
+
439
+ end
440
+ end
441
+ end
442
+ end