puppetfile-resolver 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +169 -0
  4. data/lib/puppetfile-resolver.rb +7 -0
  5. data/lib/puppetfile-resolver/cache/base.rb +28 -0
  6. data/lib/puppetfile-resolver/cache/persistent.rb +50 -0
  7. data/lib/puppetfile-resolver/data/ruby_ca_certs.pem +3432 -0
  8. data/lib/puppetfile-resolver/models.rb +8 -0
  9. data/lib/puppetfile-resolver/models/missing_module_specification.rb +27 -0
  10. data/lib/puppetfile-resolver/models/module_dependency.rb +55 -0
  11. data/lib/puppetfile-resolver/models/module_specification.rb +114 -0
  12. data/lib/puppetfile-resolver/models/puppet_dependency.rb +34 -0
  13. data/lib/puppetfile-resolver/models/puppet_specification.rb +25 -0
  14. data/lib/puppetfile-resolver/models/puppetfile_dependency.rb +14 -0
  15. data/lib/puppetfile-resolver/puppetfile.rb +22 -0
  16. data/lib/puppetfile-resolver/puppetfile/base_module.rb +62 -0
  17. data/lib/puppetfile-resolver/puppetfile/document.rb +125 -0
  18. data/lib/puppetfile-resolver/puppetfile/forge_module.rb +14 -0
  19. data/lib/puppetfile-resolver/puppetfile/git_module.rb +19 -0
  20. data/lib/puppetfile-resolver/puppetfile/invalid_module.rb +16 -0
  21. data/lib/puppetfile-resolver/puppetfile/local_module.rb +14 -0
  22. data/lib/puppetfile-resolver/puppetfile/parser/errors.rb +19 -0
  23. data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval.rb +133 -0
  24. data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/dsl.rb +51 -0
  25. data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/forge.rb +50 -0
  26. data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/git.rb +32 -0
  27. data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/invalid.rb +27 -0
  28. data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/local.rb +26 -0
  29. data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/svn.rb +30 -0
  30. data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/puppet_module.rb +36 -0
  31. data/lib/puppetfile-resolver/puppetfile/svn_module.rb +16 -0
  32. data/lib/puppetfile-resolver/puppetfile/validation_errors.rb +106 -0
  33. data/lib/puppetfile-resolver/resolution_provider.rb +182 -0
  34. data/lib/puppetfile-resolver/resolution_result.rb +30 -0
  35. data/lib/puppetfile-resolver/resolver.rb +77 -0
  36. data/lib/puppetfile-resolver/spec_searchers/common.rb +15 -0
  37. data/lib/puppetfile-resolver/spec_searchers/forge.rb +75 -0
  38. data/lib/puppetfile-resolver/spec_searchers/git.rb +64 -0
  39. data/lib/puppetfile-resolver/spec_searchers/local.rb +45 -0
  40. data/lib/puppetfile-resolver/ui/debug_ui.rb +15 -0
  41. data/lib/puppetfile-resolver/ui/null_ui.rb +20 -0
  42. data/lib/puppetfile-resolver/util.rb +23 -0
  43. data/lib/puppetfile-resolver/version.rb +5 -0
  44. data/puppetfile-cli.rb +101 -0
  45. data/spec/spec_helper.rb +48 -0
  46. data/spec/unit/puppetfile-resolver/puppetfile/document_spec.rb +316 -0
  47. data/spec/unit/puppetfile-resolver/puppetfile/parser/r10k_eval_spec.rb +460 -0
  48. data/spec/unit/puppetfile-resolver/resolver_spec.rb +421 -0
  49. metadata +124 -0
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/base_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ class ForgeModule < BaseModule
8
+ def initialize(title)
9
+ super
10
+ @module_type = FORGE_MODULE
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/base_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ class GitModule < BaseModule
8
+ attr_accessor :remote
9
+ attr_accessor :ref
10
+ attr_accessor :commit
11
+ attr_accessor :tag
12
+
13
+ def initialize(title)
14
+ super
15
+ @module_type = GIT_MODULE
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/base_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ class InvalidModule < BaseModule
8
+ attr_accessor :reason
9
+
10
+ def initialize(title)
11
+ super
12
+ @module_type = INVALID_MODULE
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/base_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ class LocalModule < BaseModule
8
+ def initialize(title)
9
+ super
10
+ @module_type = LOCAL_MODULE
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PuppetfileResolver
4
+ module Puppetfile
5
+ module Parser
6
+ class ParserError < RuntimeError
7
+ attr_accessor :location
8
+
9
+ def initialize(error_message)
10
+ @error_message = error_message
11
+ end
12
+
13
+ def to_s
14
+ @error_message
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile'
4
+ require 'puppetfile-resolver/puppetfile/parser/errors'
5
+ require 'puppetfile-resolver/puppetfile/document'
6
+ require 'puppetfile-resolver/puppetfile/parser/r10k_eval/dsl'
7
+
8
+ module PuppetfileResolver
9
+ module Puppetfile
10
+ module Parser
11
+ # Parses a Puppetfile using the instance_eval method from R10K
12
+ module R10KEval
13
+ PUPPETFILE_MONIKER ||= 'Puppetfile'
14
+
15
+ def self.parse(puppetfile_contents)
16
+ document = ::PuppetfileResolver::Puppetfile::Document.new(puppetfile_contents)
17
+
18
+ puppetfile_dsl = DSL.new(document)
19
+ begin
20
+ puppetfile_dsl.instance_eval(puppetfile_contents, PUPPETFILE_MONIKER)
21
+ rescue StandardError, LoadError => e
22
+ # Find the originating error from within the puppetfile
23
+ loc = e.backtrace_locations
24
+ .select { |item| item.absolute_path == PUPPETFILE_MONIKER }
25
+ .first
26
+ start_line_number = loc.nil? ? 0 : loc.lineno - 1 # Line numbers from ruby are base 1
27
+ end_line_number = loc.nil? ? puppetfile_contents.lines.count - 1 : loc.lineno - 1 # Line numbers from ruby are base 1
28
+ # Note - Ruby doesn't give a character position so just highlight the entire line
29
+
30
+ err = PuppetfileResolver::Puppetfile::Parser::ParserError.new(e.to_s)
31
+ err.location = PuppetfileResolver::Puppetfile::DocumentLocation.new.tap do |doc_loc|
32
+ doc_loc.start_line = start_line_number
33
+ doc_loc.end_line = end_line_number
34
+ end
35
+ raise err, e.backtrace
36
+ rescue SyntaxError => e
37
+ # Syntax Errrors are special as they don't appear in the backtrace :-(
38
+ # Sytnax Errors are _really_ horrible as they don't give you the line or character position
39
+ # as methods on the error. Instead we have to use janky regexes to get the information
40
+ matches = /^#{PUPPETFILE_MONIKER}:(\d+):/.match(e.message)
41
+ line_num = matches.nil? ? 0 : matches[1].to_i - 1 # Line numbers from ruby are base 1
42
+ # If we get a string that can't be cast to integer properly to_i returns 0, which we then take 1 from
43
+ # which results in a negative number. As a simple precaution, anything that's negative, just assume line zero
44
+ line_num = 0 if line_num < 0
45
+
46
+ err = PuppetfileResolver::Puppetfile::Parser::ParserError.new(e.to_s)
47
+ err.location = PuppetfileResolver::Puppetfile::DocumentLocation.new.tap do |doc_loc|
48
+ doc_loc.start_line = line_num
49
+ doc_loc.end_line = line_num
50
+ # We can't get character position reliably
51
+ end
52
+ raise err, e.backtrace
53
+ end
54
+
55
+ # Post process magic comments
56
+ post_process_flags!(document)
57
+
58
+ # Freeze the flags so they can't be modified
59
+ document.modules.each { |mod| mod.resolver_flags.freeze }
60
+
61
+ document
62
+ end
63
+
64
+ # Parses a Puppetfile and applies the "magic comments"
65
+ def self.post_process_flags!(document)
66
+ flag_ranges = {}
67
+ document.content.lines.each_with_index do |line, index|
68
+ if (matches = line.match(%r{^\s*# resolver:disable ([A-Za-z\/,]+)(?:\s|$)}))
69
+ flags_from_line(matches[1]).each do |flag|
70
+ # Start a flag range if there isn't already one going
71
+ next unless flag_ranges[flag].nil?
72
+ flag_ranges[flag] = index
73
+ end
74
+ elsif (matches = line.match(%r{# resolver:disable ([A-Za-z\/,]+)(?:\s|$)}))
75
+ flags_from_line(matches[1]).each do |flag|
76
+ # Assert the flag if we're not already within a range
77
+ next unless flag_ranges[flag].nil?
78
+ assert_resolver_flag(document, flag, index, index)
79
+ end
80
+ elsif (matches = line.match(%r{^\s*# resolver:enable ([A-Za-z\/,]+)(?:\s|$)}))
81
+ flags_from_line(matches[1]).each do |flag|
82
+ # End a flag range if there isn't already one going
83
+ next if flag_ranges[flag].nil?
84
+ assert_resolver_flag(document, flag, flag_ranges[flag], index)
85
+ flag_ranges.delete(flag)
86
+ end
87
+ end
88
+ end
89
+
90
+ return if flag_ranges.empty?
91
+ # Any remaining flag ranges will be at the document end
92
+ end_line = document.content.lines.count
93
+ flag_ranges.each do |flag, start_line|
94
+ assert_resolver_flag(document, flag, start_line, end_line)
95
+ end
96
+ end
97
+ private_class_method :post_process_flags!
98
+
99
+ # Extracts the flags from the text based definitions
100
+ # @return [Array[Symbol]]
101
+ def self.flags_from_line(line)
102
+ line.split(',').map do |flag_name|
103
+ case flag_name.downcase
104
+ when 'dependency/puppet'
105
+ PuppetfileResolver::Puppetfile::DISABLE_PUPPET_DEPENDENCY_FLAG
106
+ when 'dependency/all'
107
+ PuppetfileResolver::Puppetfile::DISABLE_ALL_DEPENDENCIES_FLAG
108
+ else # rubocop:disable Style/EmptyElse We will be adding something here later
109
+ # TODO: Should we log a warning/info here?
110
+ nil
111
+ end
112
+ end.compact
113
+ end
114
+ private_class_method :flags_from_line
115
+
116
+ # Sets the specified flag on modules which are between from_line to to_line
117
+ def self.assert_resolver_flag(document, flag, from_line, to_line)
118
+ document.modules.each do |mod|
119
+ # If we don't know where the module is (?) then ignore it
120
+ next if mod.location.start_line.nil? || mod.location.end_line.nil?
121
+
122
+ # If the module doesn't span the range we're looking for (from_line --> to_line) ignore it
123
+ next unless mod.location.start_line >= from_line && mod.location.start_line <= to_line ||
124
+ mod.location.end_line >= from_line && mod.location.end_line <= to_line
125
+ mod.resolver_flags << flag unless mod.resolver_flags.include?(flag)
126
+ end
127
+ nil
128
+ end
129
+ private_class_method :assert_resolver_flag
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/parser/r10k_eval/puppet_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ module Parser
8
+ module R10KEval
9
+ class DSL
10
+ def initialize(puppetfile_document)
11
+ @document = puppetfile_document
12
+ end
13
+
14
+ # @param [String] name
15
+ # @param [*Object] args
16
+ def mod(name, args = nil)
17
+ # Get the module object
18
+ mod = PuppetModule.from_puppetfile(name, args)
19
+ # Inject the file location
20
+ line_num = find_load_line_number
21
+ mod.location.start_line = line_num
22
+ mod.location.end_line = line_num
23
+ # Append to the list of modules
24
+ @document.add_module(mod)
25
+ end
26
+
27
+ # @param [String] forge
28
+ def forge(location)
29
+ @document.forge_uri = location
30
+ end
31
+
32
+ # @param [String] moduledir
33
+ def moduledir(_location)
34
+ end
35
+
36
+ def method_missing(method_name, *_args) # rubocop:disable Style/MethodMissingSuper, Style/MissingRespondToMissing
37
+ raise NoMethodError, "Unknown method #{method_name}"
38
+ end
39
+
40
+ private
41
+
42
+ def find_load_line_number
43
+ loc = Kernel.caller_locations
44
+ .find { |call_loc| call_loc.absolute_path == ::PuppetfileResolver::Puppetfile::Parser::R10KEval::PUPPETFILE_MONIKER }
45
+ loc.nil? ? 0 : loc.lineno - 1 # Line numbers from ruby are base 1
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/forge_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ module Parser
8
+ module R10KEval
9
+ module Module
10
+ class Forge
11
+ def self.implements?(name, args)
12
+ !name.match(/\A(\w+)[-\/](\w+)\Z/).nil? && valid_version?(args)
13
+ end
14
+
15
+ def self.to_document_module(title, args)
16
+ mod = ::PuppetfileResolver::Puppetfile::ForgeModule.new(title)
17
+ mod.version = args if valid_version?(args)
18
+ mod
19
+ end
20
+
21
+ def self.valid_version?(value)
22
+ return false unless value.is_a?(String) || value.is_a?(Symbol)
23
+ value == :latest || value.nil? || valid_version_string?(value)
24
+ end
25
+ private_class_method :valid_version?
26
+
27
+ # Version string matching regexes
28
+ # From Semantic Puppet gem
29
+ REGEX_NUMERIC = '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)' # Major . Minor . Patch
30
+ REGEX_PRE = '(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?' # Prerelease
31
+ REGEX_BUILD = '(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?' # Build
32
+ REGEX_FULL = REGEX_NUMERIC + REGEX_PRE + REGEX_BUILD.freeze
33
+ REGEX_FULL_RX = /\A#{REGEX_FULL}\Z/.freeze
34
+
35
+ def self.valid_version_string?(value)
36
+ match = value.match(REGEX_FULL_RX)
37
+ if match.nil?
38
+ false
39
+ else
40
+ prerelease = match[4]
41
+ prerelease.nil? || prerelease.split('.').all? { |x| x !~ /^0\d+$/ }
42
+ end
43
+ end
44
+ private_class_method :valid_version_string?
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/git_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ module Parser
8
+ module R10KEval
9
+ module Module
10
+ module Git
11
+ def self.implements?(_name, args)
12
+ args.is_a?(Hash) && args.key?(:git)
13
+ rescue StandardError
14
+ false
15
+ end
16
+
17
+ def self.to_document_module(title, args)
18
+ mod = ::PuppetfileResolver::Puppetfile::GitModule.new(title)
19
+ mod.remote = args[:git]
20
+ mod.ref = args[:ref] || args[:branch]
21
+ mod.commit = args[:commit]
22
+ mod.tag = args[:tag]
23
+ mod
24
+ end
25
+
26
+ # TODO: https://github.com/puppetlabs/r10k/blob/master/doc/puppetfile.mkd#git
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is a special module definition. It's the catchall when no other module type can handle it
4
+
5
+ require 'puppetfile-resolver/puppetfile/invalid_module'
6
+
7
+ module PuppetfileResolver
8
+ module Puppetfile
9
+ module Parser
10
+ module R10KEval
11
+ module Module
12
+ module Invalid
13
+ def self.implements?(_name, _args)
14
+ true
15
+ end
16
+
17
+ def self.to_document_module(title, args)
18
+ mod = ::PuppetfileResolver::Puppetfile::InvalidModule.new(title)
19
+ mod.reason = format("Module %<title>s with args %<args>s doesn't have an implementation. (Are you using the right arguments?)", title: title, args: args.inspect)
20
+ mod
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/local_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ module Parser
8
+ module R10KEval
9
+ module Module
10
+ class Local
11
+ def self.implements?(_name, args)
12
+ args.is_a?(Hash) && args.key?(:local)
13
+ rescue StandardError
14
+ false
15
+ end
16
+
17
+ def self.to_document_module(title, _args)
18
+ mod = ::PuppetfileResolver::Puppetfile::LocalModule.new(title)
19
+ mod
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puppetfile-resolver/puppetfile/svn_module'
4
+
5
+ module PuppetfileResolver
6
+ module Puppetfile
7
+ module Parser
8
+ module R10KEval
9
+ module Module
10
+ module Svn
11
+ def self.implements?(_name, args)
12
+ args.is_a?(Hash) && args.key?(:svn)
13
+ rescue StandardError
14
+ false
15
+ end
16
+
17
+ def self.to_document_module(title, args)
18
+ mod = ::PuppetfileResolver::Puppetfile::SvnModule.new(title)
19
+ mod.remote = args[:svn]
20
+ mod
21
+ end
22
+
23
+ # TODO: What about rev, revision, username, password?
24
+ # https://github.com/puppetlabs/r10k/blob/master/doc/puppetfile.mkd#svn
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end