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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +169 -0
- data/lib/puppetfile-resolver.rb +7 -0
- data/lib/puppetfile-resolver/cache/base.rb +28 -0
- data/lib/puppetfile-resolver/cache/persistent.rb +50 -0
- data/lib/puppetfile-resolver/data/ruby_ca_certs.pem +3432 -0
- data/lib/puppetfile-resolver/models.rb +8 -0
- data/lib/puppetfile-resolver/models/missing_module_specification.rb +27 -0
- data/lib/puppetfile-resolver/models/module_dependency.rb +55 -0
- data/lib/puppetfile-resolver/models/module_specification.rb +114 -0
- data/lib/puppetfile-resolver/models/puppet_dependency.rb +34 -0
- data/lib/puppetfile-resolver/models/puppet_specification.rb +25 -0
- data/lib/puppetfile-resolver/models/puppetfile_dependency.rb +14 -0
- data/lib/puppetfile-resolver/puppetfile.rb +22 -0
- data/lib/puppetfile-resolver/puppetfile/base_module.rb +62 -0
- data/lib/puppetfile-resolver/puppetfile/document.rb +125 -0
- data/lib/puppetfile-resolver/puppetfile/forge_module.rb +14 -0
- data/lib/puppetfile-resolver/puppetfile/git_module.rb +19 -0
- data/lib/puppetfile-resolver/puppetfile/invalid_module.rb +16 -0
- data/lib/puppetfile-resolver/puppetfile/local_module.rb +14 -0
- data/lib/puppetfile-resolver/puppetfile/parser/errors.rb +19 -0
- data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval.rb +133 -0
- data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/dsl.rb +51 -0
- data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/forge.rb +50 -0
- data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/git.rb +32 -0
- data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/invalid.rb +27 -0
- data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/local.rb +26 -0
- data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/module/svn.rb +30 -0
- data/lib/puppetfile-resolver/puppetfile/parser/r10k_eval/puppet_module.rb +36 -0
- data/lib/puppetfile-resolver/puppetfile/svn_module.rb +16 -0
- data/lib/puppetfile-resolver/puppetfile/validation_errors.rb +106 -0
- data/lib/puppetfile-resolver/resolution_provider.rb +182 -0
- data/lib/puppetfile-resolver/resolution_result.rb +30 -0
- data/lib/puppetfile-resolver/resolver.rb +77 -0
- data/lib/puppetfile-resolver/spec_searchers/common.rb +15 -0
- data/lib/puppetfile-resolver/spec_searchers/forge.rb +75 -0
- data/lib/puppetfile-resolver/spec_searchers/git.rb +64 -0
- data/lib/puppetfile-resolver/spec_searchers/local.rb +45 -0
- data/lib/puppetfile-resolver/ui/debug_ui.rb +15 -0
- data/lib/puppetfile-resolver/ui/null_ui.rb +20 -0
- data/lib/puppetfile-resolver/util.rb +23 -0
- data/lib/puppetfile-resolver/version.rb +5 -0
- data/puppetfile-cli.rb +101 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/unit/puppetfile-resolver/puppetfile/document_spec.rb +316 -0
- data/spec/unit/puppetfile-resolver/puppetfile/parser/r10k_eval_spec.rb +460 -0
- data/spec/unit/puppetfile-resolver/resolver_spec.rb +421 -0
- 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
|