puppetfile-resolver 0.1.0

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 (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