r10k 3.9.2 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec_tests.yml +1 -1
  3. data/.travis.yml +0 -10
  4. data/CHANGELOG.mkd +33 -0
  5. data/README.mkd +6 -0
  6. data/doc/dynamic-environments/configuration.mkd +25 -0
  7. data/doc/dynamic-environments/usage.mkd +26 -0
  8. data/doc/puppetfile.mkd +18 -5
  9. data/integration/Rakefile +3 -1
  10. data/integration/tests/basic_functionality/basic_deployment.rb +176 -0
  11. data/integration/tests/user_scenario/basic_workflow/negative/neg_specify_deleted_forge_module.rb +3 -9
  12. data/integration/tests/user_scenario/basic_workflow/single_env_purge_unmanaged_modules.rb +21 -25
  13. data/integration/tests/user_scenario/complex_workflow/multi_env_add_change_remove.rb +3 -3
  14. data/integration/tests/user_scenario/complex_workflow/multi_env_remove_re-add.rb +3 -3
  15. data/integration/tests/user_scenario/complex_workflow/multi_env_unamanaged.rb +3 -3
  16. data/lib/r10k/action/deploy/environment.rb +17 -2
  17. data/lib/r10k/action/deploy/module.rb +38 -7
  18. data/lib/r10k/action/puppetfile/check.rb +7 -5
  19. data/lib/r10k/action/puppetfile/install.rb +22 -16
  20. data/lib/r10k/action/puppetfile/purge.rb +12 -9
  21. data/lib/r10k/action/runner.rb +45 -10
  22. data/lib/r10k/cli/deploy.rb +5 -0
  23. data/lib/r10k/cli/puppetfile.rb +0 -1
  24. data/lib/r10k/content_synchronizer.rb +16 -4
  25. data/lib/r10k/environment/base.rb +64 -11
  26. data/lib/r10k/environment/with_modules.rb +6 -10
  27. data/lib/r10k/git/cache.rb +1 -1
  28. data/lib/r10k/git/rugged/credentials.rb +77 -0
  29. data/lib/r10k/git/stateful_repository.rb +8 -0
  30. data/lib/r10k/git.rb +3 -0
  31. data/lib/r10k/initializers.rb +4 -0
  32. data/lib/r10k/module/base.rb +42 -1
  33. data/lib/r10k/module/definition.rb +64 -0
  34. data/lib/r10k/module/forge.rb +17 -4
  35. data/lib/r10k/module/git.rb +24 -2
  36. data/lib/r10k/module/local.rb +2 -3
  37. data/lib/r10k/module/svn.rb +12 -1
  38. data/lib/r10k/module.rb +20 -2
  39. data/lib/r10k/module_loader/puppetfile/dsl.rb +42 -0
  40. data/lib/r10k/module_loader/puppetfile.rb +272 -0
  41. data/lib/r10k/puppetfile.rb +82 -160
  42. data/lib/r10k/settings/definition.rb +1 -1
  43. data/lib/r10k/settings.rb +58 -2
  44. data/lib/r10k/source/base.rb +10 -0
  45. data/lib/r10k/source/git.rb +5 -0
  46. data/lib/r10k/source/svn.rb +4 -0
  47. data/lib/r10k/util/purgeable.rb +70 -8
  48. data/lib/r10k/version.rb +1 -1
  49. data/locales/r10k.pot +165 -65
  50. data/r10k.gemspec +2 -0
  51. data/spec/fixtures/unit/action/r10k_forge_auth.yaml +4 -0
  52. data/spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml +3 -0
  53. data/spec/fixtures/unit/puppetfile/forge-override/Puppetfile +8 -0
  54. data/spec/fixtures/unit/puppetfile/various-modules/Puppetfile +9 -0
  55. data/spec/fixtures/unit/puppetfile/various-modules/Puppetfile.new +9 -0
  56. data/spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_subdir_2/ignored_1 +0 -0
  57. data/spec/fixtures/unit/util/purgeable/managed_two/.hidden/unmanaged_3 +0 -0
  58. data/spec/r10k-mocks/mock_env.rb +3 -0
  59. data/spec/r10k-mocks/mock_source.rb +7 -3
  60. data/spec/unit/action/deploy/environment_spec.rb +105 -30
  61. data/spec/unit/action/deploy/module_spec.rb +232 -42
  62. data/spec/unit/action/puppetfile/check_spec.rb +17 -5
  63. data/spec/unit/action/puppetfile/install_spec.rb +42 -36
  64. data/spec/unit/action/puppetfile/purge_spec.rb +15 -17
  65. data/spec/unit/action/runner_spec.rb +122 -26
  66. data/spec/unit/environment/base_spec.rb +30 -17
  67. data/spec/unit/environment/git_spec.rb +2 -2
  68. data/spec/unit/environment/svn_spec.rb +4 -3
  69. data/spec/unit/environment/with_modules_spec.rb +2 -1
  70. data/spec/unit/git/cache_spec.rb +14 -0
  71. data/spec/unit/git/rugged/credentials_spec.rb +29 -0
  72. data/spec/unit/git/stateful_repository_spec.rb +5 -0
  73. data/spec/unit/module/base_spec.rb +54 -8
  74. data/spec/unit/module/forge_spec.rb +59 -5
  75. data/spec/unit/module/git_spec.rb +67 -17
  76. data/spec/unit/module/svn_spec.rb +35 -5
  77. data/spec/unit/module_loader/puppetfile_spec.rb +403 -0
  78. data/spec/unit/module_spec.rb +28 -0
  79. data/spec/unit/puppetfile_spec.rb +125 -189
  80. data/spec/unit/settings_spec.rb +47 -2
  81. data/spec/unit/util/purgeable_spec.rb +38 -6
  82. metadata +28 -2
@@ -0,0 +1,64 @@
1
+ require 'r10k/module'
2
+
3
+ class R10K::Module::Definition < R10K::Module::Base
4
+
5
+ attr_reader :version
6
+
7
+ def initialize(name, dirname:, args:, implementation:, environment: nil)
8
+ @original_name = name
9
+ @original_args = args.dup
10
+ @implementation = implementation
11
+ @version = implementation.statically_defined_version(name, args)
12
+
13
+ super(name, dirname, args, environment)
14
+ end
15
+
16
+ def to_implementation
17
+ mod = @implementation.new(@title, @dirname, @original_args, @environment)
18
+
19
+ mod.origin = origin
20
+ mod.spec_deletable = spec_deletable
21
+
22
+ mod
23
+ end
24
+
25
+ # syncing is a noop for module definitions
26
+ # Returns false to inidicate the module was not updated
27
+ def sync(args = {})
28
+ logger.debug1(_("Not updating module %{name}, assuming content unchanged") % {name: name})
29
+ false
30
+ end
31
+
32
+ def status
33
+ :insync
34
+ end
35
+
36
+ def properties
37
+ type = nil
38
+
39
+ if @args[:type]
40
+ type = @args[:type]
41
+ elsif @args[:ref] || @args[:commit] || @args[:branch] || @args[:tag]
42
+ type = 'git'
43
+ elsif @args[:svn]
44
+ # This logic is clear and included for completeness sake, though at
45
+ # this time module definitions do not support SVN versions.
46
+ type = 'svn'
47
+ else
48
+ type = 'forge'
49
+ end
50
+
51
+ {
52
+ expected: version,
53
+ # We can't get the value for `actual` here because that requires the
54
+ # implementation (and potentially expensive operations by the
55
+ # implementation). Some consumers will check this value, if it exists
56
+ # and if not, fall back to the expected version. That is the correct
57
+ # behavior when assuming modules are unchanged, and why `actual` is set
58
+ # to `nil` here.
59
+ actual: nil,
60
+ type: type
61
+ }
62
+ end
63
+ end
64
+
@@ -25,6 +25,10 @@ class R10K::Module::Forge < R10K::Module::Base
25
25
  expected_version == :latest || expected_version.nil? || PuppetForge::Util.version_valid?(expected_version)
26
26
  end
27
27
 
28
+ def self.statically_defined_version(name, args)
29
+ args[:version] if args[:version].is_a?(String)
30
+ end
31
+
28
32
  # @!attribute [r] metadata
29
33
  # @api private
30
34
  # @return [PuppetForge::Metadata]
@@ -35,8 +39,6 @@ class R10K::Module::Forge < R10K::Module::Base
35
39
  # @return [PuppetForge::V3::Module] The Puppet Forge module metadata
36
40
  attr_reader :v3_module
37
41
 
38
- include R10K::Logging
39
-
40
42
  include R10K::Util::Setopts
41
43
 
42
44
  def initialize(title, dirname, opts, environment=nil)
@@ -50,7 +52,7 @@ class R10K::Module::Forge < R10K::Module::Base
50
52
  :version => :expected_version,
51
53
  :source => ::R10K::Util::Setopts::Ignore,
52
54
  :type => ::R10K::Util::Setopts::Ignore,
53
- })
55
+ }, :raise_on_unhandled => false)
54
56
 
55
57
  @expected_version ||= current_version || :latest
56
58
 
@@ -58,17 +60,24 @@ class R10K::Module::Forge < R10K::Module::Base
58
60
  end
59
61
 
60
62
  # @param [Hash] opts Deprecated
63
+ # @return [Boolean] true if the module was updated, false otherwise
61
64
  def sync(opts={})
65
+ updated = false
62
66
  if should_sync?
63
67
  case status
64
68
  when :absent
65
69
  install
70
+ updated = true
66
71
  when :outdated
67
72
  upgrade
73
+ updated = true
68
74
  when :mismatched
69
75
  reinstall
76
+ updated = true
70
77
  end
78
+ maybe_delete_spec_dir
71
79
  end
80
+ updated
72
81
  end
73
82
 
74
83
  def properties
@@ -83,7 +92,11 @@ class R10K::Module::Forge < R10K::Module::Base
83
92
  def expected_version
84
93
  if @expected_version == :latest
85
94
  begin
86
- @expected_version = @v3_module.current_release.version
95
+ if @v3_module.current_release
96
+ @expected_version = @v3_module.current_release.version
97
+ else
98
+ raise PuppetForge::ReleaseNotFound, _("The module %{title} does not appear to have any published releases, cannot determine latest version.") % { title: @title }
99
+ end
87
100
  rescue Faraday::ResourceNotFound => e
88
101
  raise PuppetForge::ReleaseNotFound, _("The module %{title} does not exist on %{url}.") % {title: @title, url: PuppetForge::V3::Release.conn.url_prefix}, e.backtrace
89
102
  end
@@ -13,6 +13,21 @@ class R10K::Module::Git < R10K::Module::Base
13
13
  false
14
14
  end
15
15
 
16
+ # Will be called if self.implement? above returns true. Will return
17
+ # the version info, if version is statically defined in the modules
18
+ # declaration.
19
+ def self.statically_defined_version(name, args)
20
+ if !args[:type] && (args[:ref] || args[:tag] || args[:commit])
21
+ if args[:ref] && args[:ref].to_s.match(/[0-9a-f]{40}/)
22
+ args[:ref]
23
+ else
24
+ args[:tag] || args[:commit]
25
+ end
26
+ elsif args[:type].to_s == 'git' && args[:version] && args[:version].to_s.match(/[0-9a-f]{40}/)
27
+ args[:version]
28
+ end
29
+ end
30
+
16
31
  # @!attribute [r] repo
17
32
  # @api private
18
33
  # @return [R10K::Git::StatefulRepository]
@@ -52,7 +67,7 @@ class R10K::Module::Git < R10K::Module::Base
52
67
  :git => :remote,
53
68
  :default_branch => :default_ref,
54
69
  :default_branch_override => :default_override_ref,
55
- })
70
+ }, :raise_on_unhandled => false)
56
71
 
57
72
  force = @overrides.dig(:modules, :force)
58
73
  @force = force == false ? false : true
@@ -83,9 +98,16 @@ class R10K::Module::Git < R10K::Module::Base
83
98
  end
84
99
 
85
100
  # @param [Hash] opts Deprecated
101
+ # @return [Boolean] true if the module was updated, false otherwise
86
102
  def sync(opts={})
87
103
  force = opts[:force] || @force
88
- @repo.sync(version, force) if should_sync?
104
+ if should_sync?
105
+ updated = @repo.sync(version, force)
106
+ else
107
+ updated = false
108
+ end
109
+ maybe_delete_spec_dir
110
+ updated
89
111
  end
90
112
 
91
113
  def status
@@ -1,5 +1,4 @@
1
1
  require 'r10k/module'
2
- require 'r10k/logging'
3
2
 
4
3
  # A dummy module type that can be used to "protect" Puppet modules that exist
5
4
  # inside of the Puppetfile "moduledir" location. Local modules will not be
@@ -12,8 +11,6 @@ class R10K::Module::Local < R10K::Module::Base
12
11
  args.is_a?(Hash) && (args[:local] || args[:type].to_s == 'local')
13
12
  end
14
13
 
15
- include R10K::Logging
16
-
17
14
  def version
18
15
  "0.0.0"
19
16
  end
@@ -31,7 +28,9 @@ class R10K::Module::Local < R10K::Module::Base
31
28
  end
32
29
 
33
30
  # @param [Hash] opts Deprecated
31
+ # @return [Boolean] false, because local modules are always considered in-sync
34
32
  def sync(opts={})
35
33
  logger.debug1 _("Module %{title} is a local module, always indicating synced.") % {title: title}
34
+ false
36
35
  end
37
36
  end
@@ -10,6 +10,10 @@ class R10K::Module::SVN < R10K::Module::Base
10
10
  args.has_key?(:svn) || args[:type].to_s == 'svn'
11
11
  end
12
12
 
13
+ def self.statically_defined_version(name, args)
14
+ nil
15
+ end
16
+
13
17
  # @!attribute [r] expected_revision
14
18
  # @return [String] The SVN revision that the repo should have checked out
15
19
  attr_reader :expected_revision
@@ -50,7 +54,7 @@ class R10K::Module::SVN < R10K::Module::Base
50
54
  :revision => :expected_revision,
51
55
  :username => :self,
52
56
  :password => :self
53
- })
57
+ }, :raise_on_unhandled => false)
54
58
 
55
59
  @working_dir = R10K::SVN::WorkingDir.new(@path, :username => @username, :password => @password)
56
60
  end
@@ -70,17 +74,24 @@ class R10K::Module::SVN < R10K::Module::Base
70
74
  end
71
75
 
72
76
  # @param [Hash] opts Deprecated
77
+ # @return [Boolean] true if the module was updated, false otherwise
73
78
  def sync(opts={})
79
+ updated = false
74
80
  if should_sync?
75
81
  case status
76
82
  when :absent
77
83
  install
84
+ updated = true
78
85
  when :mismatched
79
86
  reinstall
87
+ updated = true
80
88
  when :outdated
81
89
  update
90
+ updated = true
82
91
  end
92
+ maybe_delete_spec_dir
83
93
  end
94
+ updated
84
95
  end
85
96
 
86
97
  def exist?
data/lib/r10k/module.rb CHANGED
@@ -22,17 +22,35 @@ module R10K::Module
22
22
  #
23
23
  # @return [Object < R10K::Module] A member of the implementing subclass
24
24
  def self.new(name, basedir, args, environment=nil)
25
+ with_implementation(name, args) do |implementation|
26
+ implementation.new(name, basedir, args, environment)
27
+ end
28
+ end
29
+
30
+ # Takes the same signature as Module.new but returns an metadata module
31
+ def self.from_metadata(name, basedir, args, environment=nil)
32
+ with_implementation(name, args) do |implementation|
33
+ R10K::Module::Definition.new(name,
34
+ dirname: basedir,
35
+ args: args,
36
+ implementation: implementation,
37
+ environment: environment)
38
+ end
39
+ end
40
+
41
+ def self.with_implementation(name, args, &block)
25
42
  if implementation = @klasses.find { |klass| klass.implement?(name, args) }
26
- obj = implementation.new(name, basedir, args, environment)
27
- obj
43
+ block.call(implementation)
28
44
  else
29
45
  raise _("Module %{name} with args %{args} doesn't have an implementation. (Are you using the right arguments?)") % {name: name, args: args.inspect}
30
46
  end
31
47
  end
32
48
 
49
+
33
50
  require 'r10k/module/base'
34
51
  require 'r10k/module/git'
35
52
  require 'r10k/module/svn'
36
53
  require 'r10k/module/local'
37
54
  require 'r10k/module/forge'
55
+ require 'r10k/module/definition'
38
56
  end
@@ -0,0 +1,42 @@
1
+ module R10K
2
+ module ModuleLoader
3
+ class Puppetfile
4
+ class DSL
5
+ # A barebones implementation of the Puppetfile DSL
6
+ #
7
+ # @api private
8
+
9
+ def initialize(librarian, metadata_only: false)
10
+ @librarian = librarian
11
+ @metadata_only = metadata_only
12
+ end
13
+
14
+ def mod(name, args = nil)
15
+ if args.is_a?(Hash)
16
+ opts = args
17
+ else
18
+ opts = { version: args }
19
+ end
20
+
21
+ if @metadata_only
22
+ @librarian.add_module_metadata(name, opts)
23
+ else
24
+ @librarian.add_module(name, opts)
25
+ end
26
+ end
27
+
28
+ def forge(location)
29
+ @librarian.set_forge(location)
30
+ end
31
+
32
+ def moduledir(location)
33
+ @librarian.set_moduledir(location)
34
+ end
35
+
36
+ def method_missing(method, *args)
37
+ raise NoMethodError, _("unrecognized declaration '%{method}'") % {method: method}
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,272 @@
1
+ require 'r10k/logging'
2
+ require 'r10k/module'
3
+
4
+ module R10K
5
+ module ModuleLoader
6
+ class Puppetfile
7
+
8
+ include R10K::Logging
9
+
10
+ DEFAULT_MODULEDIR = 'modules'
11
+ DEFAULT_PUPPETFILE_NAME = 'Puppetfile'
12
+
13
+ attr_accessor :default_branch_override, :environment
14
+ attr_reader :modules, :moduledir, :puppetfile_path,
15
+ :managed_directories, :desired_contents, :purge_exclusions
16
+
17
+ # @param basedir [String] The path that contains the moduledir &
18
+ # Puppetfile by default. May be an environment, project, or
19
+ # simple directory.
20
+ # @param puppetfile [String] The path to the Puppetfile, either an
21
+ # absolute full path or a relative path with regards to the basedir.
22
+ # @param moduledir [String] The path to the moduledir, either an
23
+ # absolute full path or a relative path with regards to the basedir.
24
+ # @param forge [String] The url (without protocol) to the Forge
25
+ # @param overrides [Hash] Configuration for loaded modules' behavior
26
+ # @param environment [R10K::Environment] When provided, the environment
27
+ # in which loading takes place
28
+ def initialize(basedir:,
29
+ moduledir: DEFAULT_MODULEDIR,
30
+ puppetfile: DEFAULT_PUPPETFILE_NAME,
31
+ overrides: {},
32
+ environment: nil)
33
+
34
+ @basedir = cleanpath(basedir)
35
+ @moduledir = resolve_path(@basedir, moduledir)
36
+ @puppetfile_path = resolve_path(@basedir, puppetfile)
37
+ @overrides = overrides
38
+ @environment = environment
39
+ @default_branch_override = @overrides.dig(:environments, :default_branch_override)
40
+ @allow_puppetfile_forge = @overrides.dig(:forge, :allow_puppetfile_override)
41
+
42
+ @existing_module_metadata = []
43
+ @existing_module_versions_by_name = {}
44
+ @modules = []
45
+
46
+ @managed_directories = []
47
+ @desired_contents = []
48
+ @purge_exclusions = []
49
+ end
50
+
51
+ def load
52
+ with_readable_puppetfile(@puppetfile_path) do
53
+ self.load!
54
+ end
55
+ end
56
+
57
+ def load!
58
+ logger.info _("Using Puppetfile '%{puppetfile}'") % {puppetfile: @puppetfile_path}
59
+ logger.debug _("Using moduledir '%{moduledir}'") % {moduledir: @moduledir}
60
+
61
+ dsl = R10K::ModuleLoader::Puppetfile::DSL.new(self)
62
+ dsl.instance_eval(puppetfile_content(@puppetfile_path), @puppetfile_path)
63
+
64
+ validate_no_duplicate_names(@modules)
65
+
66
+ managed_content = @modules.group_by(&:dirname)
67
+
68
+ @managed_directories = determine_managed_directories(managed_content)
69
+ @desired_contents = determine_desired_contents(managed_content)
70
+ @purge_exclusions = determine_purge_exclusions(@managed_directories)
71
+
72
+ {
73
+ modules: @modules,
74
+ managed_directories: @managed_directories,
75
+ desired_contents: @desired_contents,
76
+ purge_exclusions: @purge_exclusions
77
+ }
78
+
79
+ rescue SyntaxError, LoadError, ArgumentError, NameError => e
80
+ raise R10K::Error.wrap(e, _("Failed to evaluate %{path}") % {path: @puppetfile_path})
81
+ end
82
+
83
+ def load_metadata
84
+ with_readable_puppetfile(@puppetfile_path) do
85
+ self.load_metadata!
86
+ end
87
+ end
88
+
89
+ def load_metadata!
90
+ dsl = R10K::ModuleLoader::Puppetfile::DSL.new(self, metadata_only: true)
91
+ dsl.instance_eval(puppetfile_content(@puppetfile_path), @puppetfile_path)
92
+
93
+ @existing_module_versions_by_name = @existing_module_metadata.map {|mod| [ mod.name, mod.version ] }.to_h
94
+ empty_load_output.merge(modules: @existing_module_metadata)
95
+
96
+ rescue SyntaxError, LoadError, ArgumentError, NameError => e
97
+ logger.warn _("Unable to preload Puppetfile because of %{msg}" % { msg: e.message })
98
+ end
99
+
100
+ def add_module_metadata(name, info)
101
+ install_path, metadata_info, _ = parse_module_definition(name, info)
102
+
103
+ mod = R10K::Module.from_metadata(name, install_path, metadata_info, @environment)
104
+
105
+ @existing_module_metadata << mod
106
+ end
107
+
108
+ ##
109
+ ## set_forge, set_moduledir, and add_module are used directly by the DSL class
110
+ ##
111
+
112
+ # @param [String] forge
113
+ def set_forge(forge)
114
+ if @allow_puppetfile_forge
115
+ logger.debug _("Using Forge from Puppetfile: %{forge}") % { forge: forge }
116
+ PuppetForge.host = forge
117
+ else
118
+ logger.debug _("Ignoring Forge declaration in Puppetfile, using value from settings: %{forge}.") % { forge: PuppetForge.host }
119
+ end
120
+ end
121
+
122
+ # @param [String] moduledir
123
+ def set_moduledir(moduledir)
124
+ @moduledir = resolve_path(@basedir, moduledir)
125
+ end
126
+
127
+ # @param [String] name
128
+ # @param [Hash, String, Symbol, nil] info Calling with
129
+ # anything but a Hash is deprecated. The DSL will now convert
130
+ # String and Symbol versions to Hashes of the shape
131
+ # { version: <String or Symbol> }
132
+ #
133
+ # String inputs should be valid module versions, the Symbol
134
+ # `:latest` is allowed, as well as `nil`.
135
+ #
136
+ # Non-Hash inputs are only ever used by Forge modules. In
137
+ # future versions this method will require the caller (the
138
+ # DSL class, not the Puppetfile author) to do this conversion
139
+ # itself.
140
+ #
141
+ def add_module(name, info)
142
+ install_path, metadata_info, spec_deletable = parse_module_definition(name, info)
143
+
144
+ mod = R10K::Module.from_metadata(name, install_path, metadata_info, @environment)
145
+ mod.origin = :puppetfile
146
+ mod.spec_deletable = spec_deletable
147
+
148
+ # Do not save modules if they would conflict with the attached
149
+ # environment
150
+ if @environment && @environment.module_conflicts?(mod)
151
+ return @modules
152
+ end
153
+
154
+ # If this module's metadata has a static version and that version
155
+ # matches the existing module declaration use it, otherwise create
156
+ # a regular module to sync.
157
+ unless mod.version && (mod.version == @existing_module_versions_by_name[mod.name])
158
+ mod = mod.to_implementation
159
+ end
160
+
161
+ @modules << mod
162
+ end
163
+
164
+ private
165
+
166
+ def empty_load_output
167
+ {
168
+ modules: [],
169
+ managed_directories: [],
170
+ desired_contents: [],
171
+ purge_exclusions: []
172
+ }
173
+ end
174
+
175
+ def with_readable_puppetfile(puppetfile_path, &block)
176
+ if File.readable?(puppetfile_path)
177
+ block.call
178
+ else
179
+ logger.debug _("Puppetfile %{path} missing or unreadable") % {path: puppetfile_path.inspect}
180
+
181
+ empty_load_output
182
+ end
183
+ end
184
+
185
+ def parse_module_definition(name, info)
186
+ if !info.is_a?(Hash)
187
+ info = { version: info }
188
+ end
189
+
190
+ info[:overrides] = @overrides
191
+
192
+ if @default_branch_override
193
+ info[:default_branch_override] = @default_branch_override
194
+ end
195
+
196
+ spec_deletable = false
197
+ if install_path = info.delete(:install_path)
198
+ install_path = resolve_path(@basedir, install_path)
199
+ validate_install_path(install_path, name)
200
+ else
201
+ install_path = @moduledir
202
+ spec_deletable = true
203
+ end
204
+
205
+ return [ install_path, info, spec_deletable ]
206
+ end
207
+
208
+ # @param [Array<R10K::Module>] modules
209
+ def validate_no_duplicate_names(modules)
210
+ dupes = modules
211
+ .group_by { |mod| mod.name }
212
+ .select { |_, mods| mods.size > 1 }
213
+ .map(&:first)
214
+ unless dupes.empty?
215
+ msg = _('Puppetfiles cannot contain duplicate module names.')
216
+ msg += ' '
217
+ msg += _("Remove the duplicates of the following modules: %{dupes}" % { dupes: dupes.join(' ') })
218
+ raise R10K::Error.new(msg)
219
+ end
220
+ end
221
+
222
+ def resolve_path(base, path)
223
+ if Pathname.new(path).absolute?
224
+ cleanpath(path)
225
+ else
226
+ cleanpath(File.join(base, path))
227
+ end
228
+ end
229
+
230
+ def validate_install_path(path, modname)
231
+ unless /^#{Regexp.escape(@basedir)}.*/ =~ path
232
+ raise R10K::Error.new("Puppetfile cannot manage content '#{modname}' outside of containing environment: #{path} is not within #{@basedir}")
233
+ end
234
+
235
+ true
236
+ end
237
+
238
+ def determine_managed_directories(managed_content)
239
+ managed_content.keys.reject { |dir| dir == @basedir }
240
+ end
241
+
242
+ # Returns an array of the full paths to all the content being managed.
243
+ # @return [Array<String>]
244
+ def determine_desired_contents(managed_content)
245
+ managed_content.flat_map do |install_path, mods|
246
+ mods.collect { |mod| File.join(install_path, mod.name) }
247
+ end
248
+ end
249
+
250
+ def determine_purge_exclusions(managed_dirs)
251
+ if environment && environment.respond_to?(:desired_contents)
252
+ managed_dirs + environment.desired_contents
253
+ else
254
+ managed_dirs
255
+ end
256
+ end
257
+
258
+ # .cleanpath is as close to a canonical path as we can do without touching
259
+ # the filesystem. The .realpath methods will choke if some of the
260
+ # intermediate paths are missing, even though in some cases we will create
261
+ # them later as needed.
262
+ def cleanpath(path)
263
+ Pathname.new(path).cleanpath.to_s
264
+ end
265
+
266
+ # For testing purposes only
267
+ def puppetfile_content(path)
268
+ File.read(path)
269
+ end
270
+ end
271
+ end
272
+ end