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
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --tag ~slow
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in librarian.gemspec
4
+ gemspec
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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $:.unshift(lib) unless $:.include?(lib)
5
+
6
+ require 'librarian/chef/cli'
7
+ Librarian::Chef::Cli.bin!
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path('../../lib', __FILE__)
4
+ $:.unshift(lib) unless $:.include?(lib)
5
+
6
+ require 'librarian/mock/cli'
7
+ Librarian::Mock::Cli.bin!
@@ -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
+
@@ -0,0 +1,5 @@
1
+ require 'aruba/cucumber'
2
+
3
+ Before do
4
+ @aruba_timeout_seconds = 2
5
+ end
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/cli'
2
+ require 'librarian/chef'
3
+ require 'librarian/chef/particularity'
4
+
5
+ module Librarian
6
+ module Chef
7
+ class Cli < Librarian::Cli
8
+
9
+ include Particularity
10
+ extend Particularity
11
+
12
+ end
13
+ end
14
+ end
@@ -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