librarian 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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