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
data/.rspec
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 ApplicationsOnline, LLC.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
Librarian
|
2
|
+
=========
|
3
|
+
|
4
|
+
A tool to resolve recursively a set of specifications and fetch and install the fully resolved specifications.
|
5
|
+
|
6
|
+
Librarian::Mock
|
7
|
+
---------------
|
8
|
+
|
9
|
+
An adapter for Librarian for unit testing the general features.
|
10
|
+
The mock source is in-process and in-memory and does not touch the filesystem or the network.
|
11
|
+
|
12
|
+
Librarian::Chef
|
13
|
+
---------------
|
14
|
+
|
15
|
+
An adapter for Librarian applying to Chef cookbooks in a Chef Repository.
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
|
19
|
+
$ cd ~/path/to/chef-repo
|
20
|
+
# put dependencies and their sources into ./Cheffile
|
21
|
+
|
22
|
+
# resolve dependencies:
|
23
|
+
$ librarian-chef resolve [--clean] [--verbose]
|
24
|
+
|
25
|
+
# install dependencies into ./cookbooks
|
26
|
+
$ librarian-chef install [--clean] [--verbose]
|
27
|
+
|
28
|
+
# update your cheffile with new/changed/removed constraints/sources/dependencies
|
29
|
+
$ librarian-chef install [--verbose]
|
30
|
+
|
31
|
+
# update the version of a dependency
|
32
|
+
$ librarian-chef update dependency-1 dependency-2 dependency-3 [--verbose]
|
33
|
+
|
34
|
+
You should `.gitignore` your `./cookbooks` directory.
|
35
|
+
If you are manually tracking/vendoring outside cookbooks within the repository,
|
36
|
+
put them in another directory such as `./cookbooks-sources` and use the `:path` source.
|
37
|
+
You should typically not need to do this.
|
38
|
+
|
39
|
+
License
|
40
|
+
-------
|
41
|
+
|
42
|
+
Written by Jay Feldblum.
|
43
|
+
|
44
|
+
Copyright (c) 2011 ApplicationsOnline, LLC.
|
45
|
+
|
46
|
+
Released under the terms of the MIT License.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
require 'cucumber/rake/task'
|
9
|
+
Cucumber::Rake::Task.new(:features)
|
10
|
+
|
11
|
+
task :default => [:spec, :features]
|
12
|
+
rescue LoadError
|
13
|
+
end
|
data/bin/librarian-chef
ADDED
data/bin/librarian-mock
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default: --tags ~@wip --format progress
|
@@ -0,0 +1,69 @@
|
|
1
|
+
Feature: cli/catalog
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
Background:
|
6
|
+
Given a directory named "cookbooks"
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
Scenario: A simple Cheffile with one cookbook
|
11
|
+
Given a file named "cookbook-sources/apt/metadata.yaml" with:
|
12
|
+
"""
|
13
|
+
name: apt
|
14
|
+
version: 1.0.0
|
15
|
+
dependencies: { }
|
16
|
+
"""
|
17
|
+
Given a file named "Cheffile" with:
|
18
|
+
"""
|
19
|
+
cookbook 'apt',
|
20
|
+
:path => 'cookbook-sources'
|
21
|
+
"""
|
22
|
+
When I run "librarian-chef install --verbose"
|
23
|
+
Then the exit status should be 0
|
24
|
+
And the file "cookbooks/apt/metadata.yaml" should contain exactly:
|
25
|
+
"""
|
26
|
+
name: apt
|
27
|
+
version: 1.0.0
|
28
|
+
dependencies: { }
|
29
|
+
"""
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
Scenario: A simple Cheffile with one cookbook with one dependency
|
34
|
+
Given a file named "cookbook-sources/main/metadata.yaml" with:
|
35
|
+
"""
|
36
|
+
name: main
|
37
|
+
version: 1.0.0
|
38
|
+
dependencies:
|
39
|
+
sub: 1.0.0
|
40
|
+
"""
|
41
|
+
Given a file named "cookbook-sources/sub/metadata.yaml" with:
|
42
|
+
"""
|
43
|
+
name: sub
|
44
|
+
version: 1.0.0
|
45
|
+
dependencies: {}
|
46
|
+
"""
|
47
|
+
Given a file named "Cheffile" with:
|
48
|
+
"""
|
49
|
+
path 'cookbook-sources'
|
50
|
+
cookbook 'main'
|
51
|
+
"""
|
52
|
+
When I run "librarian-chef install --verbose"
|
53
|
+
Then the exit status should be 0
|
54
|
+
And the file "cookbooks/main/metadata.yaml" should contain exactly:
|
55
|
+
"""
|
56
|
+
name: main
|
57
|
+
version: 1.0.0
|
58
|
+
dependencies:
|
59
|
+
sub: 1.0.0
|
60
|
+
"""
|
61
|
+
And the file "cookbooks/sub/metadata.yaml" should contain exactly:
|
62
|
+
"""
|
63
|
+
name: sub
|
64
|
+
version: 1.0.0
|
65
|
+
dependencies: {}
|
66
|
+
"""
|
67
|
+
|
68
|
+
|
69
|
+
|
data/lib/librarian.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
require 'librarian/helpers/debug'
|
4
|
+
require 'librarian/support/abstract_method'
|
5
|
+
|
6
|
+
require 'librarian/version'
|
7
|
+
require 'librarian/dependency'
|
8
|
+
require 'librarian/manifest_set'
|
9
|
+
require 'librarian/particularity'
|
10
|
+
require 'librarian/resolver'
|
11
|
+
require 'librarian/source'
|
12
|
+
require 'librarian/spec_change_set'
|
13
|
+
require 'librarian/specfile'
|
14
|
+
require 'librarian/lockfile'
|
15
|
+
require 'librarian/ui'
|
16
|
+
|
17
|
+
module Librarian
|
18
|
+
extend self
|
19
|
+
|
20
|
+
include Support::AbstractMethod
|
21
|
+
include Helpers::Debug
|
22
|
+
|
23
|
+
class Error < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :ui
|
27
|
+
|
28
|
+
abstract_method :specfile_name, :dsl_class, :install_path
|
29
|
+
|
30
|
+
def project_path
|
31
|
+
@project_path ||= begin
|
32
|
+
root = Pathname.new(Dir.pwd)
|
33
|
+
root = root.dirname until root.join(specfile_name).exist? || root.dirname == root
|
34
|
+
path = root.join(specfile_name)
|
35
|
+
path.exist? ? root : nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def specfile_path
|
40
|
+
project_path.join(specfile_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def specfile
|
44
|
+
Specfile.new(self, specfile_path)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile_name
|
48
|
+
"#{specfile_name}.lock"
|
49
|
+
end
|
50
|
+
|
51
|
+
def lockfile_path
|
52
|
+
project_path.join(lockfile_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def lockfile
|
56
|
+
Lockfile.new(self, lockfile_path)
|
57
|
+
end
|
58
|
+
|
59
|
+
def ephemeral_lockfile
|
60
|
+
Lockfile.new(self, nil)
|
61
|
+
end
|
62
|
+
|
63
|
+
def resolver
|
64
|
+
Resolver.new(self)
|
65
|
+
end
|
66
|
+
|
67
|
+
def cache_path
|
68
|
+
project_path.join('tmp/librarian/cache')
|
69
|
+
end
|
70
|
+
|
71
|
+
def project_relative_path_to(path)
|
72
|
+
Pathname.new(path).relative_path_from(project_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
def spec_change_set(spec, lock)
|
76
|
+
SpecChangeSet.new(self, spec, lock)
|
77
|
+
end
|
78
|
+
|
79
|
+
def ensure!
|
80
|
+
unless project_path
|
81
|
+
raise Error, "Cannot find #{specfile_name}!"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def clean!
|
86
|
+
if cache_path.exist?
|
87
|
+
debug { "Deleting #{project_relative_path_to(cache_path)}" }
|
88
|
+
cache_path.rmtree
|
89
|
+
end
|
90
|
+
if install_path.exist?
|
91
|
+
install_path.children.each do |c|
|
92
|
+
debug { "Deleting #{project_relative_path_to(c)}" }
|
93
|
+
c.rmtree unless c.file?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
if lockfile_path.exist?
|
97
|
+
debug { "Deleting #{project_relative_path_to(lockfile_path)}" }
|
98
|
+
lockfile_path.rmtree
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def install!
|
103
|
+
resolve!
|
104
|
+
manifests = ManifestSet.sort(lockfile.load(lockfile_path.read).manifests)
|
105
|
+
manifests.each do |manifest|
|
106
|
+
manifest.source.cache!([manifest])
|
107
|
+
end
|
108
|
+
manifests.each do |manifest|
|
109
|
+
manifest.install!
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def update!(dependency_names)
|
114
|
+
unless lockfile_path.exist?
|
115
|
+
raise Error, "Lockfile missing!"
|
116
|
+
end
|
117
|
+
previous_resolution = lockfile.load(lockfile_path.read)
|
118
|
+
partial_manifests = ManifestSet.deep_strip(previous_resolution.manifests, dependency_names)
|
119
|
+
debug { "Precaching Sources:" }
|
120
|
+
previous_resolution.sources.each do |source|
|
121
|
+
debug { " #{source}" }
|
122
|
+
end
|
123
|
+
spec = specfile.read(previous_resolution.sources)
|
124
|
+
spec_changes = spec_change_set(spec, previous_resolution)
|
125
|
+
raise Error, "Cannot update when the specfile has been changed." unless spec_changes.same?
|
126
|
+
resolution = resolver.resolve(spec, partial_manifests)
|
127
|
+
unless resolution.correct?
|
128
|
+
ui.info { "Could not resolve the dependencies." }
|
129
|
+
else
|
130
|
+
lockfile_text = lockfile.save(resolution)
|
131
|
+
debug { "Bouncing #{lockfile_name}" }
|
132
|
+
bounced_lockfile_text = lockfile.save(lockfile.load(lockfile_text))
|
133
|
+
unless bounced_lockfile_text == lockfile_text
|
134
|
+
debug { "lockfile_text: \n#{lockfile_text}"}
|
135
|
+
debug { "bounced_lockfile_text: \n#{bounced_lockfile_text}"}
|
136
|
+
raise Error, "Cannot bounce #{lockfile_name}!"
|
137
|
+
end
|
138
|
+
lockfile_path.open('wb') { |f| f.write(lockfile_text) }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def resolve!(options = {})
|
143
|
+
if options[:force] || !lockfile_path.exist?
|
144
|
+
spec = specfile.read
|
145
|
+
manifests = []
|
146
|
+
else
|
147
|
+
lock = lockfile.read
|
148
|
+
debug { "Precaching Sources:" }
|
149
|
+
lock.sources.each do |source|
|
150
|
+
debug { " #{source}" }
|
151
|
+
end
|
152
|
+
spec = specfile.read(lock.sources)
|
153
|
+
changes = spec_change_set(spec, lock)
|
154
|
+
if changes.same?
|
155
|
+
debug { "The specfile is unchanged: nothing to do." }
|
156
|
+
return
|
157
|
+
end
|
158
|
+
manifests = changes.analyze
|
159
|
+
end
|
160
|
+
|
161
|
+
resolution = resolver.resolve(spec, manifests)
|
162
|
+
unless resolution.correct?
|
163
|
+
raise Error, "Could not resolve the dependencies."
|
164
|
+
else
|
165
|
+
lockfile_text = lockfile.save(resolution)
|
166
|
+
debug { "Bouncing #{lockfile_name}" }
|
167
|
+
bounced_lockfile_text = lockfile.save(lockfile.load(lockfile_text))
|
168
|
+
unless bounced_lockfile_text == lockfile_text
|
169
|
+
debug { "lockfile_text: \n#{lockfile_text}"}
|
170
|
+
debug { "bounced_lockfile_text: \n#{bounced_lockfile_text}"}
|
171
|
+
raise Error, "Cannot bounce #{lockfile_name}!"
|
172
|
+
end
|
173
|
+
lockfile_path.open('wb') { |f| f.write(lockfile_text) }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def dsl(&block)
|
178
|
+
dsl_class.run(&block)
|
179
|
+
end
|
180
|
+
|
181
|
+
def dsl_class
|
182
|
+
self::Dsl
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def root_module
|
188
|
+
self
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'librarian/chef/extension'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'librarian/dsl'
|
2
|
+
require 'librarian/chef/source'
|
3
|
+
|
4
|
+
module Librarian
|
5
|
+
module Chef
|
6
|
+
class Dsl < Librarian::Dsl
|
7
|
+
dependency :cookbook
|
8
|
+
|
9
|
+
source :site => Source::Site
|
10
|
+
source :git => Source::Git
|
11
|
+
source :path => Source::Path
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'librarian/specfile'
|
2
|
+
require 'librarian/source'
|
3
|
+
require 'librarian/chef/dsl'
|
4
|
+
require 'librarian/chef/source'
|
5
|
+
|
6
|
+
module Librarian
|
7
|
+
module Chef
|
8
|
+
extend self
|
9
|
+
extend Librarian
|
10
|
+
|
11
|
+
module Overrides
|
12
|
+
def specfile_name
|
13
|
+
'Cheffile'
|
14
|
+
end
|
15
|
+
|
16
|
+
def install_path
|
17
|
+
project_path.join('cookbooks')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
extend Overrides
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|