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