librarian 0.0.25 → 0.0.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +6 -1
  4. data/lib/librarian/action/persist_resolution_mixin.rb +51 -0
  5. data/lib/librarian/action/resolve.rb +3 -38
  6. data/lib/librarian/action/update.rb +4 -38
  7. data/lib/librarian/chef/dsl.rb +1 -0
  8. data/lib/librarian/chef/source.rb +1 -0
  9. data/lib/librarian/chef/source/github.rb +27 -0
  10. data/lib/librarian/chef/source/site.rb +51 -51
  11. data/lib/librarian/cli.rb +31 -23
  12. data/lib/librarian/cli/manifest_presenter.rb +36 -22
  13. data/lib/librarian/dependency.rb +60 -0
  14. data/lib/librarian/environment.rb +13 -1
  15. data/lib/librarian/linter/source_linter.rb +55 -0
  16. data/lib/librarian/lockfile/parser.rb +39 -16
  17. data/lib/librarian/manifest.rb +8 -0
  18. data/lib/librarian/manifest_set.rb +5 -7
  19. data/lib/librarian/mock/source/mock.rb +4 -21
  20. data/lib/librarian/resolution.rb +1 -1
  21. data/lib/librarian/resolver.rb +15 -12
  22. data/lib/librarian/resolver/implementation.rb +166 -75
  23. data/lib/librarian/source/basic_api.rb +45 -0
  24. data/lib/librarian/source/git.rb +4 -22
  25. data/lib/librarian/source/git/repository.rb +1 -1
  26. data/lib/librarian/source/local.rb +0 -7
  27. data/lib/librarian/source/path.rb +4 -22
  28. data/lib/librarian/version.rb +1 -1
  29. data/librarian.gemspec +3 -3
  30. data/spec/functional/chef/source/site_spec.rb +150 -100
  31. data/spec/functional/source/git/repository_spec.rb +2 -1
  32. data/spec/{functional → integration}/chef/source/git_spec.rb +12 -3
  33. data/spec/integration/chef/source/site_spec.rb +217 -0
  34. data/spec/support/cli_macro.rb +4 -12
  35. data/spec/support/method_patch_macro.rb +30 -0
  36. data/spec/unit/config/database_spec.rb +8 -0
  37. data/spec/unit/dependency_spec.rb +176 -0
  38. data/spec/unit/environment_spec.rb +76 -7
  39. data/spec/unit/resolver_spec.rb +2 -2
  40. metadata +52 -46
@@ -23,18 +23,20 @@ module Librarian
23
23
 
24
24
  class << self
25
25
  def bin!
26
- with_environment do |environment|
27
- begin
28
- start
29
- rescue Librarian::Error => e
30
- environment.ui.error e.message
31
- environment.ui.debug e.backtrace.join("\n")
32
- exit (e.respond_to?(:status_code) ? e.status_code : 1)
33
- rescue Interrupt => e
34
- environment.ui.error "\nQuitting..."
35
- exit 1
36
- end
37
- end
26
+ status = with_environment { returning_status { start } }
27
+ exit status
28
+ end
29
+
30
+ def returning_status
31
+ yield
32
+ 0
33
+ rescue Librarian::Error => e
34
+ environment.ui.error e.message
35
+ environment.ui.debug e.backtrace.join("\n")
36
+ e.respond_to?(:status_code) && e.status_code || 1
37
+ rescue Interrupt => e
38
+ environment.ui.error "\nQuitting..."
39
+ 1
38
40
  end
39
41
 
40
42
  attr_accessor :environment
@@ -74,16 +76,13 @@ module Librarian
74
76
  if key
75
77
  raise Error, "cannot set both value and delete" if value && options["delete"]
76
78
  if options["delete"]
77
- raise Error, "must set either global or local" unless options["global"] ^ options["local"]
78
- scope = options["global"] ? :global : options["local"] ? :local : nil
79
+ scope = config_scope(true)
79
80
  environment.config_db[key, scope] = nil
80
81
  elsif value
81
- raise Error, "must set either global or local" unless options["global"] ^ options["local"]
82
- scope = options["global"] ? :global : options["local"] ? :local : nil
82
+ scope = config_scope(true)
83
83
  environment.config_db[key, scope] = value
84
84
  else
85
- raise Error, "cannot set both global and local" if options["global"] && options["local"]
86
- scope = options["global"] ? :global : options["local"] ? :local : nil
85
+ scope = config_scope(false)
87
86
  if value = environment.config_db[key, scope]
88
87
  prefix = scope ? "#{key} (#{scope})" : key
89
88
  say "#{prefix}: #{value}"
@@ -123,11 +122,9 @@ module Librarian
123
122
  def outdated
124
123
  ensure!
125
124
  resolution = environment.lock
126
- resolution.manifests.sort_by(&:name).each do |manifest|
127
- source = manifest.source
128
- source_manifest = source.manifests(manifest.name).first
129
- next if manifest.version == source_manifest.version
130
- say "#{manifest.name} (#{manifest.version} -> #{source_manifest.version})"
125
+ manifests = resolution.manifests.sort_by(&:name)
126
+ manifests.select(&:outdated?).each do |manifest|
127
+ say "#{manifest.name} (#{manifest.version} -> #{manifest.latest.version})"
131
128
  end
132
129
  end
133
130
 
@@ -211,5 +208,16 @@ module Librarian
211
208
  environment.logger.relative_path_to(path)
212
209
  end
213
210
 
211
+ def config_scope(exclusive)
212
+ g, l = "global", "local"
213
+ if exclusive
214
+ options[g] ^ options[l] or raise Error, "must set either #{g} or #{l}"
215
+ else
216
+ options[g] && options[l] and raise Error, "cannot set both #{g} and #{l}"
217
+ end
218
+
219
+ options[g] ? :global : options[l] ? :local : nil
220
+ end
221
+
214
222
  end
215
223
  end
@@ -17,40 +17,47 @@ module Librarian
17
17
  full = options[:detailed]
18
18
  full = !names.empty? if full.nil?
19
19
 
20
- if names.empty?
21
- names = manifests.map(&:name).sort if names.empty?
22
- else
23
- missing_names = names.reject{|name| manifest(name)}
24
- unless missing_names.empty?
25
- raise Error, "not found: #{missing_names.map(&:inspect).join(', ')}"
26
- end
27
- end
20
+ names = manifests.map(&:name).sort if names.empty?
21
+ assert_no_manifests_missing!(names)
28
22
 
29
- names.each do |name|
30
- manifest = manifest(name)
31
- present_one(manifest, :detailed => full)
32
- end
23
+ present_each(names, :detailed => full)
33
24
  end
34
25
 
35
26
  def present_one(manifest, options = { })
36
27
  full = options[:detailed]
37
28
 
38
29
  say "#{manifest.name} (#{manifest.version})" do
39
- if full
40
- say "source: #{manifest.source}"
41
- unless manifest.dependencies.empty?
42
- say "dependencies:" do
43
- manifest.dependencies.sort_by(&:name).each do |dependency|
44
- say "#{dependency.name} (#{dependency.requirement})"
45
- end
46
- end
47
- end
48
- end
30
+ full or next
31
+
32
+ present_one_source(manifest)
33
+ present_one_dependencies(manifest)
49
34
  end
50
35
  end
51
36
 
52
37
  private
53
38
 
39
+ def present_each(names, options)
40
+ names.each do |name|
41
+ manifest = manifest(name)
42
+ present_one(manifest, options)
43
+ end
44
+ end
45
+
46
+ def present_one_source(manifest)
47
+ say "source: #{manifest.source}"
48
+ end
49
+
50
+ def present_one_dependencies(manifest)
51
+ manifest.dependencies.empty? and return
52
+
53
+ say "dependencies:" do
54
+ deps = manifest.dependencies.sort_by(&:name)
55
+ deps.each do |dependency|
56
+ say "#{dependency.name} (#{dependency.requirement})"
57
+ end
58
+ end
59
+ end
60
+
54
61
  attr_accessor :scope_level, :manifests_index
55
62
 
56
63
  def manifest(name)
@@ -74,6 +81,13 @@ module Librarian
74
81
  self.scope_level = original_scope_level
75
82
  end
76
83
 
84
+ def assert_no_manifests_missing!(names)
85
+ missing_names = names.reject{|name| manifest(name)}
86
+ unless missing_names.empty?
87
+ raise Error, "not found: #{missing_names.map(&:inspect).join(', ')}"
88
+ end
89
+ end
90
+
77
91
  end
78
92
  end
79
93
  end
@@ -26,6 +26,51 @@ module Librarian
26
26
  to_gem_requirement.to_s
27
27
  end
28
28
 
29
+ COMPATS_TABLE = {
30
+ %w(= = ) => lambda{|s, o| s == o},
31
+ %w(= !=) => lambda{|s, o| s != o},
32
+ %w(= > ) => lambda{|s, o| s > o},
33
+ %w(= < ) => lambda{|s, o| s < o},
34
+ %w(= >=) => lambda{|s, o| s >= o},
35
+ %w(= <=) => lambda{|s, o| s <= o},
36
+ %w(= ~>) => lambda{|s, o| s >= o && s.release < o.bump},
37
+ %w(!= !=) => true,
38
+ %w(!= > ) => true,
39
+ %w(!= < ) => true,
40
+ %w(!= >=) => true,
41
+ %w(!= <=) => true,
42
+ %w(!= ~>) => true,
43
+ %w(> > ) => true,
44
+ %w(> < ) => lambda{|s, o| s < o},
45
+ %w(> >=) => true,
46
+ %w(> <=) => lambda{|s, o| s < o},
47
+ %w(> ~>) => lambda{|s, o| s < o.bump},
48
+ %w(< < ) => true,
49
+ %w(< >=) => lambda{|s, o| s > o},
50
+ %w(< <=) => true,
51
+ %w(< ~>) => lambda{|s, o| s > o},
52
+ %w(>= >=) => true,
53
+ %w(>= <=) => lambda{|s, o| s <= o},
54
+ %w(>= ~>) => lambda{|s, o| s < o.bump},
55
+ %w(<= <=) => true,
56
+ %w(<= ~>) => lambda{|s, o| s >= o},
57
+ %w(~> ~>) => lambda{|s, o| s < o.bump && s.bump > o},
58
+ }
59
+
60
+ def consistent_with?(other)
61
+ sgreq, ogreq = to_gem_requirement, other.to_gem_requirement
62
+ sreqs, oreqs = sgreq.requirements, ogreq.requirements
63
+ sreqs.all? do |sreq|
64
+ oreqs.all? do |oreq|
65
+ compatible?(sreq, oreq)
66
+ end
67
+ end
68
+ end
69
+
70
+ def inconsistent_with?(other)
71
+ !consistent_with?(other)
72
+ end
73
+
29
74
  protected
30
75
 
31
76
  attr_accessor :backing
@@ -38,6 +83,13 @@ module Librarian
38
83
  arg
39
84
  end
40
85
  end
86
+
87
+ def compatible?(a, b)
88
+ a, b = b, a unless COMPATS_TABLE.include?([a.first, b.first])
89
+ r = COMPATS_TABLE[[a.first, b.first]]
90
+ r = r.call(a.last, b.last) if r.respond_to?(:call)
91
+ r
92
+ end
41
93
  end
42
94
 
43
95
  attr_accessor :name, :requirement, :source
@@ -77,6 +129,14 @@ module Librarian
77
129
  self.source == other.source
78
130
  end
79
131
 
132
+ def consistent_with?(other)
133
+ name != other.name || requirement.consistent_with?(other.requirement)
134
+ end
135
+
136
+ def inconsistent_with?(other)
137
+ !consistent_with?(other)
138
+ end
139
+
80
140
  private
81
141
 
82
142
  def assert_name_valid!(name)
@@ -153,7 +153,9 @@ module Librarian
153
153
  end
154
154
  end
155
155
 
156
- def net_http_class
156
+ def net_http_class(host)
157
+ return Net::HTTP if no_proxy?(host)
158
+
157
159
  @net_http_class ||= begin
158
160
  p = http_proxy_uri
159
161
  p ? Net::HTTP::Proxy(p.host, p.port, p.user, p.password) : Net::HTTP
@@ -166,5 +168,15 @@ module Librarian
166
168
  self
167
169
  end
168
170
 
171
+ def no_proxy?(host)
172
+ @no_proxy ||= begin
173
+ list = ENV['NO_PROXY'] || ENV['no_proxy'] || ""
174
+ list.split(/\s*,\s*/) + %w(localhost 127.0.0.1)
175
+ end
176
+ @no_proxy.any? do |host_addr|
177
+ host.match(Regexp.quote(host_addr)+'$')
178
+ end
179
+ end
180
+
169
181
  end
170
182
  end
@@ -0,0 +1,55 @@
1
+ module Librarian
2
+ module Linter
3
+ class SourceLinter
4
+
5
+ class << self
6
+ def lint!(klass)
7
+ new(klass).lint!
8
+ end
9
+ end
10
+
11
+ attr_accessor :klass
12
+ private :klass=
13
+
14
+ def initialize(klass)
15
+ self.klass = klass
16
+ end
17
+
18
+ def lint!
19
+ lint_class_responds_to! *[
20
+ :lock_name,
21
+ :from_spec_args,
22
+ :from_lock_options,
23
+ ]
24
+
25
+ lint_instance_responds_to! *[
26
+ :to_spec_args,
27
+ :to_lock_options,
28
+ :manifests,
29
+ :fetch_version,
30
+ :fetch_dependencies,
31
+ :pinned?,
32
+ :unpin!,
33
+ :install!,
34
+ ]
35
+ end
36
+
37
+ private
38
+
39
+ def lint_class_responds_to!(*names)
40
+ missing = names.reject{|name| klass.respond_to?(name)}
41
+ return if missing.empty?
42
+
43
+ raise "class must respond to #{missing.join(', ')}"
44
+ end
45
+
46
+ def lint_instance_responds_to!(*names)
47
+ missing = names - klass.public_instance_methods.map(&:to_sym)
48
+ return if missing.empty?
49
+
50
+ raise "instance must respond to #{missing.join(', ')}"
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -21,10 +21,18 @@ module Librarian
21
21
  end
22
22
 
23
23
  def parse(string)
24
- string = string.dup
25
- source_type_names_map = Hash[dsl_class.source_types.map{|t| [t[1].lock_name, t[1]]}]
26
- source_type_names = dsl_class.source_types.map{|t| t[1].lock_name}
27
- lines = string.split(/(\r|\n|\r\n)+/).select{|l| l =~ /\S/}
24
+ lines = string.lines.map{|l| l.sub(/\s+\z/, '')}.reject(&:empty?)
25
+ sources = extract_and_parse_sources(lines)
26
+ manifests = compile(sources)
27
+ manifests_index = Hash[manifests.map{|m| [m.name, m]}]
28
+ raise StandardError, "Expected DEPENDENCIES topic!" unless lines.shift == "DEPENDENCIES"
29
+ dependencies = extract_and_parse_dependencies(lines, manifests_index)
30
+ Resolution.new(dependencies, manifests)
31
+ end
32
+
33
+ private
34
+
35
+ def extract_and_parse_sources(lines)
28
36
  sources = []
29
37
  while source_type_names.include?(lines.first)
30
38
  source = {}
@@ -50,21 +58,20 @@ module Librarian
50
58
  source[:manifests] = manifests
51
59
  sources << source
52
60
  end
53
- manifests = compile(sources)
54
- manifests_index = Hash[manifests.map{|m| [m.name, m]}]
55
- raise StandardError, "Expected DEPENDENCIES topic!" unless lines.shift == "DEPENDENCIES"
61
+ sources
62
+ end
63
+
64
+ def extract_and_parse_dependencies(lines, manifests_index)
56
65
  dependencies = []
57
66
  while lines.first =~ /^ {2}([\w-]+)(?: \((.*)\))?$/
58
67
  lines.shift
59
68
  name, requirement = $1, $2.split(/,\s*/)
60
69
  dependencies << Dependency.new(name, requirement, manifests_index[name].source)
61
70
  end
62
- Resolution.new(dependencies, manifests)
71
+ dependencies
63
72
  end
64
73
 
65
- private
66
-
67
- def compile(sources_ast)
74
+ def compile_placeholder_manifests(sources_ast)
68
75
  manifests = {}
69
76
  sources_ast.each do |source_ast|
70
77
  source_type = source_ast[:type]
@@ -78,15 +85,19 @@ module Librarian
78
85
  )
79
86
  end
80
87
  end
88
+ manifests
89
+ end
90
+
91
+ def compile(sources_ast)
92
+ manifests = compile_placeholder_manifests(sources_ast)
81
93
  manifests = manifests.map do |name, manifest|
82
94
  dependencies = manifest.dependencies.map do |d|
83
95
  Dependency.new(d.name, d.requirement, manifests[d.name].source)
84
96
  end
85
- manifest.source.manifest(
86
- manifest.name,
87
- manifest.version,
88
- dependencies
89
- )
97
+ real = Manifest.new(manifest.source, manifest.name)
98
+ real.version = manifest.version
99
+ real.dependencies = manifest.dependencies
100
+ real
90
101
  end
91
102
  ManifestSet.sort(manifests)
92
103
  end
@@ -95,6 +106,18 @@ module Librarian
95
106
  environment.dsl_class
96
107
  end
97
108
 
109
+ def source_type_names_map
110
+ @source_type_names_map ||= begin
111
+ Hash[dsl_class.source_types.map{|t| [t[1].lock_name, t[1]]}]
112
+ end
113
+ end
114
+
115
+ def source_type_names
116
+ @source_type_names ||= begin
117
+ dsl_class.source_types.map{|t| t[1].lock_name}
118
+ end
119
+ end
120
+
98
121
  end
99
122
  end
100
123
  end
@@ -65,6 +65,14 @@ module Librarian
65
65
  defined_version == fetched_version
66
66
  end
67
67
 
68
+ def latest
69
+ @latest ||= source.manifests(name).first
70
+ end
71
+
72
+ def outdated?
73
+ latest.version > version
74
+ end
75
+
68
76
  def dependencies
69
77
  defined_dependencies || fetched_dependencies
70
78
  end
@@ -43,7 +43,7 @@ module Librarian
43
43
  end
44
44
 
45
45
  def initialize(manifests)
46
- self.index = Hash === manifests ? manifests.dup : Hash[manifests.map{|m| [m.name, m]}]
46
+ self.index = Hash === manifests ? manifests.dup : index_by(manifests, &:name)
47
47
  end
48
48
 
49
49
  def to_a
@@ -76,9 +76,6 @@ module Librarian
76
76
  end
77
77
 
78
78
  def deep_strip!(names)
79
- names = Array === names ? names.dup : names.to_a
80
- assert_strings!(names)
81
-
82
79
  strippables = dependencies_of(names)
83
80
  shallow_strip!(strippables)
84
81
 
@@ -102,9 +99,6 @@ module Librarian
102
99
  end
103
100
 
104
101
  def deep_keep!(names)
105
- names = Array === names ? names.dup : names.to_a
106
- assert_strings!(names)
107
-
108
102
  keepables = dependencies_of(names)
109
103
  shallow_keep!(keepables)
110
104
 
@@ -149,5 +143,9 @@ module Librarian
149
143
  deps.to_a
150
144
  end
151
145
 
146
+ def index_by(enum)
147
+ Hash[enum.map{|obj| [yield(obj), obj]}]
148
+ end
149
+
152
150
  end
153
151
  end