librarian 0.0.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.
Files changed (65) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +46 -0
  6. data/Rakefile +13 -0
  7. data/bin/librarian-chef +7 -0
  8. data/bin/librarian-mock +7 -0
  9. data/config/cucumber.yaml +1 -0
  10. data/features/chef/cli/install.feature +69 -0
  11. data/features/support/env.rb +5 -0
  12. data/lib/librarian.rb +191 -0
  13. data/lib/librarian/chef.rb +1 -0
  14. data/lib/librarian/chef/cli.rb +14 -0
  15. data/lib/librarian/chef/dsl.rb +14 -0
  16. data/lib/librarian/chef/extension.rb +24 -0
  17. data/lib/librarian/chef/manifest.rb +43 -0
  18. data/lib/librarian/chef/particularity.rb +9 -0
  19. data/lib/librarian/chef/source.rb +3 -0
  20. data/lib/librarian/chef/source/git.rb +14 -0
  21. data/lib/librarian/chef/source/local.rb +80 -0
  22. data/lib/librarian/chef/source/path.rb +14 -0
  23. data/lib/librarian/chef/source/site.rb +271 -0
  24. data/lib/librarian/cli.rb +76 -0
  25. data/lib/librarian/dependency.rb +44 -0
  26. data/lib/librarian/dsl.rb +76 -0
  27. data/lib/librarian/dsl/receiver.rb +46 -0
  28. data/lib/librarian/dsl/target.rb +164 -0
  29. data/lib/librarian/helpers.rb +13 -0
  30. data/lib/librarian/helpers/debug.rb +35 -0
  31. data/lib/librarian/lockfile.rb +31 -0
  32. data/lib/librarian/lockfile/compiler.rb +69 -0
  33. data/lib/librarian/lockfile/parser.rb +102 -0
  34. data/lib/librarian/manifest.rb +88 -0
  35. data/lib/librarian/manifest_set.rb +131 -0
  36. data/lib/librarian/mock.rb +1 -0
  37. data/lib/librarian/mock/cli.rb +14 -0
  38. data/lib/librarian/mock/dsl.rb +14 -0
  39. data/lib/librarian/mock/extension.rb +28 -0
  40. data/lib/librarian/mock/particularity.rb +7 -0
  41. data/lib/librarian/mock/source.rb +1 -0
  42. data/lib/librarian/mock/source/mock.rb +88 -0
  43. data/lib/librarian/mock/source/mock/registry.rb +79 -0
  44. data/lib/librarian/particularity.rb +7 -0
  45. data/lib/librarian/resolution.rb +36 -0
  46. data/lib/librarian/resolver.rb +139 -0
  47. data/lib/librarian/source.rb +2 -0
  48. data/lib/librarian/source/git.rb +91 -0
  49. data/lib/librarian/source/git/repository.rb +82 -0
  50. data/lib/librarian/source/local.rb +33 -0
  51. data/lib/librarian/source/path.rb +52 -0
  52. data/lib/librarian/spec.rb +11 -0
  53. data/lib/librarian/spec_change_set.rb +169 -0
  54. data/lib/librarian/specfile.rb +16 -0
  55. data/lib/librarian/support/abstract_method.rb +21 -0
  56. data/lib/librarian/ui.rb +64 -0
  57. data/lib/librarian/version.rb +3 -0
  58. data/librarian.gemspec +29 -0
  59. data/spec/chef/git_source_spec.rb +93 -0
  60. data/spec/dsl_spec.rb +167 -0
  61. data/spec/lockfile_spec.rb +44 -0
  62. data/spec/meta/requires_spec.rb +27 -0
  63. data/spec/resolver_spec.rb +172 -0
  64. data/spec/spec_change_set_spec.rb +165 -0
  65. metadata +172 -0
@@ -0,0 +1,43 @@
1
+ require 'json'
2
+ require 'yaml'
3
+
4
+ require 'librarian/manifest'
5
+
6
+ module Librarian
7
+ module Chef
8
+ class Manifest < Manifest
9
+
10
+ module Helpers
11
+
12
+ MANIFESTS = %w(metadata.json metadata.yml metadata.yaml metadata.rb)
13
+
14
+ def manifest_path(path)
15
+ MANIFESTS.map{|s| path.join(s)}.find{|s| s.exist?}
16
+ end
17
+
18
+ def read_manifest(name, manifest_path)
19
+ case manifest_path.extname
20
+ when ".json" then JSON.parse(manifest_path.read)
21
+ when ".yml", ".yaml" then YAML.load(manifest_path.read)
22
+ when ".rb" then compile_manifest(name, manifest_path.dirname)
23
+ end
24
+ end
25
+
26
+ def compile_manifest(name, path)
27
+ # Inefficient, if there are many cookbooks with uncompiled metadata.
28
+ require 'chef/json_compat'
29
+ require 'chef/cookbook/metadata'
30
+ md = ::Chef::Cookbook::Metadata.new
31
+ md.name(name)
32
+ md.from_file(path.join('metadata.rb').to_s)
33
+ JSON.parse(::Chef::JSONCompat.to_json_pretty(md))
34
+ end
35
+
36
+ end
37
+
38
+ include Helpers
39
+ extend Helpers
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,9 @@
1
+ module Librarian
2
+ module Chef
3
+ module Particularity
4
+ def root_module
5
+ Chef
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ require 'librarian/chef/source/path'
2
+ require 'librarian/chef/source/git'
3
+ require 'librarian/chef/source/site'
@@ -0,0 +1,14 @@
1
+ require 'librarian/source/git'
2
+ require 'librarian/chef/source/local'
3
+ require 'librarian/chef/particularity'
4
+
5
+ module Librarian
6
+ module Chef
7
+ module Source
8
+ class Git < Librarian::Source::Git
9
+ include Particularity
10
+ include Local
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,80 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+
4
+ require 'librarian/chef/manifest'
5
+
6
+ module Librarian
7
+ module Chef
8
+ module Source
9
+ module Local
10
+
11
+ class Manifest < Manifest
12
+
13
+ class << self
14
+
15
+ def create(source, dependency, path)
16
+ new(source, dependency.name, path)
17
+ end
18
+
19
+ def manifest?(dependency, path)
20
+ path = Pathname.new(path)
21
+ manifest_path = manifest_path(path)
22
+ manifest_path && check_manifest(dependency, manifest_path)
23
+ end
24
+
25
+ def check_manifest(dependency, manifest_path)
26
+ manifest = read_manifest(dependency.name, manifest_path)
27
+ manifest["name"] == dependency.name
28
+ end
29
+
30
+ end
31
+
32
+ attr_reader :path
33
+
34
+ def initialize(source, name, path)
35
+ super(source, name)
36
+ @path = Pathname.new(path)
37
+ @found_path = nil
38
+ end
39
+
40
+ def found_path
41
+ @found_path ||= source.manifest_search_paths(self).find{|p| self.class.manifest?(self, p)}
42
+ end
43
+
44
+ def manifest
45
+ @manifest ||= fetch_manifest!
46
+ end
47
+
48
+ def fetch_manifest!
49
+ read_manifest(name, manifest_path(found_path))
50
+ end
51
+
52
+ def fetch_version!
53
+ manifest['version']
54
+ end
55
+
56
+ def fetch_dependencies!
57
+ manifest['dependencies']
58
+ end
59
+
60
+ def install!
61
+ debug { "Installing #{name}-#{version}" }
62
+ install_path = root_module.install_path.join(name)
63
+ if install_path.exist?
64
+ debug { "Deleting #{relative_path_to(install_path)}" }
65
+ install_path.rmtree
66
+ end
67
+ debug { "Copying #{relative_path_to(found_path)} to #{relative_path_to(install_path)}" }
68
+ FileUtils.cp_r(found_path, install_path)
69
+ end
70
+
71
+ end
72
+
73
+ def manifest_class
74
+ Manifest
75
+ end
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,14 @@
1
+ require 'librarian/source/path'
2
+ require 'librarian/chef/source/local'
3
+ require 'librarian/chef/particularity'
4
+
5
+ module Librarian
6
+ module Chef
7
+ module Source
8
+ class Path < Librarian::Source::Path
9
+ include Particularity
10
+ include Local
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,271 @@
1
+ require 'fileutils'
2
+ require 'pathname'
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'digest'
7
+
8
+ require 'librarian/helpers/debug'
9
+
10
+ require 'librarian/manifest'
11
+ require 'librarian/chef/manifest'
12
+ require 'librarian/chef/particularity'
13
+
14
+ module Librarian
15
+ module Chef
16
+ module Source
17
+ class Site
18
+
19
+ class Manifest < Manifest
20
+
21
+ attr_reader :version_uri
22
+ attr_reader :install_path
23
+
24
+ def initialize(source, name, version_uri = nil)
25
+ super(source, name)
26
+ @version_uri = version_uri
27
+
28
+ @cache_path = nil
29
+ @metadata_cache_path = nil
30
+ @package_cache_path = nil
31
+ @install_path = root_module.install_path.join(name)
32
+
33
+ @version_metadata = nil
34
+ @version_manifest = nil
35
+ end
36
+
37
+ def fetch_version!
38
+ version_metadata['version']
39
+ end
40
+
41
+ def fetch_dependencies!
42
+ version_manifest['dependencies'].map{|k, v| Dependency.new(k, v, nil)}
43
+ end
44
+
45
+ def version_uri
46
+ @version_uri ||= begin
47
+ source.cache!([self])
48
+ source.manifests(self).find{|m| m.version == version}.version_uri
49
+ end
50
+ end
51
+
52
+ def version_uri=(version_uri)
53
+ @version_uri = version_uri
54
+ end
55
+
56
+ def cache_path
57
+ @cache_path ||= source.version_cache_path(self, version_uri)
58
+ end
59
+ def metadata_cache_path
60
+ @metadata_cache_path ||= cache_path.join('version.json')
61
+ end
62
+ def package_cache_path
63
+ @package_cache_path ||= cache_path.join('package')
64
+ end
65
+
66
+ def version_metadata
67
+ @version_metadata ||= fetch_version_metadata!
68
+ end
69
+
70
+ def fetch_version_metadata!
71
+ source.cache_version_metadata!(self, version_uri)
72
+ JSON.parse(metadata_cache_path.read)
73
+ end
74
+
75
+ def version_manifest
76
+ @version_manifest ||= fetch_version_manifest!
77
+ end
78
+
79
+ def fetch_version_manifest!
80
+ source.cache_version_package!(self, version_uri, version_metadata['file'])
81
+ manifest_path = manifest_path(package_cache_path)
82
+ read_manifest(name, manifest_path)
83
+ end
84
+
85
+ def install!
86
+ debug { "Installing #{self}" }
87
+ version_manifest # make sure it's cached
88
+ if install_path.exist?
89
+ debug { "Deleting #{relative_path_to(install_path)}" }
90
+ install_path.rmtree
91
+ end
92
+ package_cache_path = source.version_package_cache_path(self, version_uri)
93
+ debug { "Copying #{relative_path_to(package_cache_path)} to #{relative_path_to(install_path)}" }
94
+ FileUtils.cp_r(package_cache_path, install_path)
95
+ end
96
+
97
+ end
98
+
99
+ include Helpers::Debug
100
+ include Particularity
101
+
102
+ class << self
103
+ LOCK_NAME = 'SITE'
104
+ def lock_name
105
+ LOCK_NAME
106
+ end
107
+ def from_lock_options(options)
108
+ new(options[:remote], options.reject{|k, v| k == :remote})
109
+ end
110
+ end
111
+
112
+ attr_reader :uri
113
+
114
+ def initialize(uri, options = {})
115
+ @uri = uri
116
+ @cache_path = nil
117
+ end
118
+
119
+ def to_s
120
+ uri
121
+ end
122
+
123
+ def ==(other)
124
+ other &&
125
+ self.class == other.class &&
126
+ self.uri == other.uri
127
+ end
128
+
129
+ def to_spec_args
130
+ [uri, {}]
131
+ end
132
+
133
+ def to_lock_options
134
+ {:remote => uri}
135
+ end
136
+
137
+ def cache!(dependencies)
138
+ cache_path.mkpath
139
+ dependencies.each do |dependency|
140
+ cache_metadata!(dependency)
141
+ end
142
+ end
143
+
144
+ # NOTE:
145
+ # Assumes the Opscode Site API responds with versions in reverse sorted order
146
+ def manifests(dependency)
147
+ metadata = JSON.parse(metadata_cache_path(dependency).read)
148
+ metadata['versions'].map{|version_uri| Manifest.new(self, dependency.name, version_uri)}
149
+ end
150
+
151
+ def manifest(name, version, dependencies)
152
+ manifest = Manifest.new(self, name)
153
+ manifest.version = version
154
+ manifest.dependencies = dependencies
155
+ manifest
156
+ end
157
+
158
+ def install_path(dependency)
159
+ root_module.install_path.join(dependency.name)
160
+ end
161
+
162
+ def cache_path
163
+ @cache_path ||= begin
164
+ dir = Digest::MD5.hexdigest(uri)
165
+ root_module.cache_path.join("source/chef/site/#{dir}")
166
+ end
167
+ end
168
+
169
+ def dependency_cache_path(dependency)
170
+ cache_path.join(dependency.name)
171
+ end
172
+
173
+ def metadata_cache_path(dependency)
174
+ dependency_cache_path(dependency).join("metadata.json")
175
+ end
176
+
177
+ def version_cache_path(dependency, version_uri)
178
+ dependency_cache_path(dependency).join(Digest::MD5.hexdigest(version_uri))
179
+ end
180
+
181
+ def version_metadata_cache_path(dependency, version_uri)
182
+ version_cache_path(dependency, version_uri).join("version.json")
183
+ end
184
+
185
+ def version_archive_cache_file(dependency, version_uri)
186
+ Pathname.new("archive.tgz")
187
+ end
188
+
189
+ def version_archive_cache_path(dependency, version_uri)
190
+ version_archive_cache_file = version_archive_cache_file(dependency, version_uri)
191
+ version_cache_path(dependency, version_uri).join(version_archive_cache_file)
192
+ end
193
+
194
+ def version_unpacked_cache_file(dependency, version_uri)
195
+ Pathname.new(dependency.name)
196
+ end
197
+
198
+ def version_unpacked_cache_path(dependency, version_uri)
199
+ version_unpacked_cache_file = version_unpacked_cache_file(dependency, version_uri)
200
+ version_cache_path(dependency, version_uri).join(version_unpacked_cache_file)
201
+ end
202
+
203
+ def version_package_cache_file(dependency, version_uri)
204
+ Pathname.new("package")
205
+ end
206
+
207
+ def version_package_cache_path(dependency, version_uri)
208
+ version_package_cache_file = version_package_cache_file(dependency, version_uri)
209
+ version_cache_path(dependency, version_uri).join(version_package_cache_file)
210
+ end
211
+
212
+ def dependency_uri(dependency)
213
+ "#{uri}/cookbooks/#{dependency.name}"
214
+ end
215
+
216
+ def cache_metadata!(dependency)
217
+ dependency_cache_path = cache_path.join(dependency.name)
218
+ dependency_cache_path.mkpath
219
+ metadata_cache_path = metadata_cache_path(dependency)
220
+ unless metadata_cache_path.exist?
221
+ dep_uri = URI.parse(dependency_uri(dependency))
222
+ debug { "Caching #{dep_uri}" }
223
+ http = Net::HTTP.new(dep_uri.host, dep_uri.port)
224
+ request = Net::HTTP::Get.new(dep_uri.path)
225
+ response = http.start{|http| http.request(request)}
226
+ unless Net::HTTPSuccess === response
227
+ raise Error, "Could not cache #{dependency} from #{dep_uri} because #{response.code} #{response.message}!"
228
+ end
229
+ metadata_blob = response.body
230
+ JSON.parse(metadata_blob) # check that it's JSON
231
+ metadata_cache_path(dependency).open('wb') do |f|
232
+ f.write(metadata_blob)
233
+ end
234
+ end
235
+ end
236
+
237
+ def cache_version_metadata!(dependency, version_uri)
238
+ version_cache_path = version_cache_path(dependency, version_uri)
239
+ unless version_cache_path.exist?
240
+ version_cache_path.mkpath
241
+ debug { "Caching #{version_uri}" }
242
+ version_metadata_blob = Net::HTTP.get(URI.parse(version_uri))
243
+ JSON.parse(version_metadata_blob) # check that it's JSON
244
+ version_metadata_cache_path(dependency, version_uri).open('wb') do |f|
245
+ f.write(version_metadata_blob)
246
+ end
247
+ end
248
+ end
249
+
250
+ def cache_version_package!(dependency, version_uri, file_uri)
251
+ version_archive_cache_path = version_archive_cache_path(dependency, version_uri)
252
+ unless version_archive_cache_path.exist?
253
+ version_archive_cache_path.open('wb') do |f|
254
+ f.write(Net::HTTP.get(URI.parse(file_uri)))
255
+ end
256
+ end
257
+ version_package_cache_path = version_package_cache_path(dependency, version_uri)
258
+ unless version_package_cache_path.exist?
259
+ dependency_cache_path = dependency_cache_path(dependency)
260
+ Dir.chdir(dependency_cache_path) do
261
+ `tar -xzf #{version_archive_cache_path}`
262
+ end
263
+ version_unpacked_temp_path = dependency_cache_path.join(dependency.name)
264
+ FileUtils.move(version_unpacked_temp_path, version_package_cache_path)
265
+ end
266
+ end
267
+
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,76 @@
1
+ require 'thor'
2
+ require 'thor/actions'
3
+ require 'librarian'
4
+
5
+ module Librarian
6
+ class Cli < Thor
7
+
8
+ include Thor::Actions
9
+ include Particularity
10
+ extend Particularity
11
+
12
+ class << self
13
+ def bin!
14
+ begin
15
+ start
16
+ rescue Librarian::Error => e
17
+ root_module.ui.error e.message
18
+ root_module.ui.debug e.backtrace.join("\n")
19
+ exit (e.respond_to?(:status_code) ? e.status_code : 1)
20
+ rescue Interrupt => e
21
+ root_module.ui.error "\nQuitting..."
22
+ exit 1
23
+ end
24
+ end
25
+ end
26
+
27
+ def initialize(*)
28
+ super
29
+ the_shell = (options["no-color"] ? Thor::Shell::Basic.new : shell)
30
+ root_module.ui = UI::Shell.new(the_shell)
31
+ root_module.ui.debug! if options["verbose"]
32
+ root_module.ui.debug_line_numbers! if options["verbose"] && options["line-numbers"]
33
+ end
34
+
35
+ desc "clean", "Cleans out the cache and install paths."
36
+ method_option "verbose"
37
+ method_option "line-numbers"
38
+ def clean
39
+ root_module.ensure!
40
+ root_module.clean!
41
+ end
42
+
43
+ desc "install", "Installs all of the dependencies you specify."
44
+ method_option "verbose"
45
+ method_option "line-numbers"
46
+ method_option "clean"
47
+ def install
48
+ root_module.ensure!
49
+ root_module.clean! if options["clean"]
50
+ root_module.install!
51
+ end
52
+
53
+ desc "resolve", "Resolves the dependencies you specify."
54
+ method_option "verbose"
55
+ method_option "line-numbers"
56
+ method_option "clean"
57
+ def resolve
58
+ root_module.ensure!
59
+ root_module.clean! if options["clean"]
60
+ root_module.resolve!
61
+ end
62
+
63
+ desc "update", "Updates the dependencies you specify."
64
+ method_option "verbose"
65
+ method_option "line-numbers"
66
+ def update(*names)
67
+ root_module.ensure!
68
+ if names.empty?
69
+ root_module.resolve!(:force => true)
70
+ else
71
+ root_module.update!(names)
72
+ end
73
+ end
74
+
75
+ end
76
+ end