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.
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +46 -0
- data/Rakefile +13 -0
- data/bin/librarian-chef +7 -0
- data/bin/librarian-mock +7 -0
- data/config/cucumber.yaml +1 -0
- data/features/chef/cli/install.feature +69 -0
- data/features/support/env.rb +5 -0
- data/lib/librarian.rb +191 -0
- data/lib/librarian/chef.rb +1 -0
- data/lib/librarian/chef/cli.rb +14 -0
- data/lib/librarian/chef/dsl.rb +14 -0
- data/lib/librarian/chef/extension.rb +24 -0
- data/lib/librarian/chef/manifest.rb +43 -0
- data/lib/librarian/chef/particularity.rb +9 -0
- data/lib/librarian/chef/source.rb +3 -0
- data/lib/librarian/chef/source/git.rb +14 -0
- data/lib/librarian/chef/source/local.rb +80 -0
- data/lib/librarian/chef/source/path.rb +14 -0
- data/lib/librarian/chef/source/site.rb +271 -0
- data/lib/librarian/cli.rb +76 -0
- data/lib/librarian/dependency.rb +44 -0
- data/lib/librarian/dsl.rb +76 -0
- data/lib/librarian/dsl/receiver.rb +46 -0
- data/lib/librarian/dsl/target.rb +164 -0
- data/lib/librarian/helpers.rb +13 -0
- data/lib/librarian/helpers/debug.rb +35 -0
- data/lib/librarian/lockfile.rb +31 -0
- data/lib/librarian/lockfile/compiler.rb +69 -0
- data/lib/librarian/lockfile/parser.rb +102 -0
- data/lib/librarian/manifest.rb +88 -0
- data/lib/librarian/manifest_set.rb +131 -0
- data/lib/librarian/mock.rb +1 -0
- data/lib/librarian/mock/cli.rb +14 -0
- data/lib/librarian/mock/dsl.rb +14 -0
- data/lib/librarian/mock/extension.rb +28 -0
- data/lib/librarian/mock/particularity.rb +7 -0
- data/lib/librarian/mock/source.rb +1 -0
- data/lib/librarian/mock/source/mock.rb +88 -0
- data/lib/librarian/mock/source/mock/registry.rb +79 -0
- data/lib/librarian/particularity.rb +7 -0
- data/lib/librarian/resolution.rb +36 -0
- data/lib/librarian/resolver.rb +139 -0
- data/lib/librarian/source.rb +2 -0
- data/lib/librarian/source/git.rb +91 -0
- data/lib/librarian/source/git/repository.rb +82 -0
- data/lib/librarian/source/local.rb +33 -0
- data/lib/librarian/source/path.rb +52 -0
- data/lib/librarian/spec.rb +11 -0
- data/lib/librarian/spec_change_set.rb +169 -0
- data/lib/librarian/specfile.rb +16 -0
- data/lib/librarian/support/abstract_method.rb +21 -0
- data/lib/librarian/ui.rb +64 -0
- data/lib/librarian/version.rb +3 -0
- data/librarian.gemspec +29 -0
- data/spec/chef/git_source_spec.rb +93 -0
- data/spec/dsl_spec.rb +167 -0
- data/spec/lockfile_spec.rb +44 -0
- data/spec/meta/requires_spec.rb +27 -0
- data/spec/resolver_spec.rb +172 -0
- data/spec/spec_change_set_spec.rb +165 -0
- 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,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
|