pdk-akerl 1.9.1.1 → 1.14.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +180 -0
  3. data/README.md +43 -4
  4. data/lib/pdk.rb +4 -2
  5. data/lib/pdk/analytics.rb +44 -0
  6. data/lib/pdk/analytics/client/google_analytics.rb +141 -0
  7. data/lib/pdk/analytics/client/noop.rb +23 -0
  8. data/lib/pdk/analytics/util.rb +17 -0
  9. data/lib/pdk/answer_file.rb +4 -1
  10. data/lib/pdk/cli.rb +50 -3
  11. data/lib/pdk/cli/build.rb +10 -4
  12. data/lib/pdk/cli/bundle.rb +10 -8
  13. data/lib/pdk/cli/config.rb +20 -0
  14. data/lib/pdk/cli/config/get.rb +24 -0
  15. data/lib/pdk/cli/console.rb +148 -0
  16. data/lib/pdk/cli/convert.rb +7 -2
  17. data/lib/pdk/cli/exec.rb +22 -190
  18. data/lib/pdk/cli/exec/command.rb +238 -0
  19. data/lib/pdk/cli/exec/interactive_command.rb +114 -0
  20. data/lib/pdk/cli/exec_group.rb +6 -6
  21. data/lib/pdk/cli/module/build.rb +0 -2
  22. data/lib/pdk/cli/module/generate.rb +4 -2
  23. data/lib/pdk/cli/new.rb +2 -0
  24. data/lib/pdk/cli/new/class.rb +2 -2
  25. data/lib/pdk/cli/new/defined_type.rb +4 -2
  26. data/lib/pdk/cli/new/module.rb +5 -0
  27. data/lib/pdk/cli/new/provider.rb +4 -2
  28. data/lib/pdk/cli/new/task.rb +4 -1
  29. data/lib/pdk/cli/new/test.rb +53 -0
  30. data/lib/pdk/cli/new/transport.rb +27 -0
  31. data/lib/pdk/cli/test.rb +0 -1
  32. data/lib/pdk/cli/test/unit.rb +18 -13
  33. data/lib/pdk/cli/update.rb +25 -3
  34. data/lib/pdk/cli/util.rb +111 -14
  35. data/lib/pdk/cli/util/interview.rb +10 -2
  36. data/lib/pdk/cli/util/option_validator.rb +4 -0
  37. data/lib/pdk/cli/util/spinner.rb +13 -0
  38. data/lib/pdk/cli/validate.rb +16 -5
  39. data/lib/pdk/config.rb +121 -0
  40. data/lib/pdk/config/analytics_schema.json +26 -0
  41. data/lib/pdk/config/errors.rb +5 -0
  42. data/lib/pdk/config/json.rb +34 -0
  43. data/lib/pdk/config/json_schema_namespace.rb +143 -0
  44. data/lib/pdk/config/json_schema_setting.rb +53 -0
  45. data/lib/pdk/config/json_with_schema.rb +50 -0
  46. data/lib/pdk/config/namespace.rb +332 -0
  47. data/lib/pdk/config/setting.rb +132 -0
  48. data/lib/pdk/config/yaml.rb +43 -0
  49. data/lib/pdk/config/yaml_with_schema.rb +59 -0
  50. data/lib/pdk/generate.rb +10 -3
  51. data/lib/pdk/generate/defined_type.rb +1 -0
  52. data/lib/pdk/generate/module.rb +62 -35
  53. data/lib/pdk/generate/provider.rb +0 -5
  54. data/lib/pdk/generate/puppet_class.rb +1 -0
  55. data/lib/pdk/generate/puppet_object.rb +88 -41
  56. data/lib/pdk/generate/transport.rb +87 -0
  57. data/lib/pdk/logger.rb +21 -1
  58. data/lib/pdk/module.rb +2 -2
  59. data/lib/pdk/module/build.rb +103 -10
  60. data/lib/pdk/module/convert.rb +85 -19
  61. data/lib/pdk/module/metadata.rb +17 -12
  62. data/lib/pdk/module/templatedir.rb +108 -40
  63. data/lib/pdk/module/update.rb +27 -15
  64. data/lib/pdk/module/update_manager.rb +23 -15
  65. data/lib/pdk/report.rb +4 -3
  66. data/lib/pdk/report/event.rb +8 -6
  67. data/lib/pdk/template_file.rb +1 -1
  68. data/lib/pdk/tests/unit.rb +48 -21
  69. data/lib/pdk/util.rb +29 -63
  70. data/lib/pdk/util/bundler.rb +19 -15
  71. data/lib/pdk/util/filesystem.rb +64 -1
  72. data/lib/pdk/util/git.rb +52 -1
  73. data/lib/pdk/util/puppet_strings.rb +123 -0
  74. data/lib/pdk/util/puppet_version.rb +27 -12
  75. data/lib/pdk/util/ruby_version.rb +30 -7
  76. data/lib/pdk/util/template_uri.rb +281 -0
  77. data/lib/pdk/util/vendored_file.rb +28 -24
  78. data/lib/pdk/util/version.rb +7 -8
  79. data/lib/pdk/util/windows.rb +1 -0
  80. data/lib/pdk/util/windows/api_types.rb +0 -7
  81. data/lib/pdk/util/windows/file.rb +1 -1
  82. data/lib/pdk/util/windows/string.rb +1 -1
  83. data/lib/pdk/validate/base_validator.rb +12 -14
  84. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -4
  85. data/lib/pdk/validate/metadata/metadata_syntax.rb +5 -3
  86. data/lib/pdk/validate/metadata_validator.rb +0 -2
  87. data/lib/pdk/validate/puppet/puppet_epp.rb +137 -0
  88. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -3
  89. data/lib/pdk/validate/puppet/puppet_syntax.rb +5 -5
  90. data/lib/pdk/validate/puppet_validator.rb +2 -3
  91. data/lib/pdk/validate/ruby/rubocop.rb +1 -6
  92. data/lib/pdk/validate/ruby_validator.rb +0 -2
  93. data/lib/pdk/validate/tasks/metadata_lint.rb +9 -5
  94. data/lib/pdk/validate/tasks/name.rb +5 -3
  95. data/lib/pdk/validate/tasks_validator.rb +0 -2
  96. data/lib/pdk/validate/yaml/syntax.rb +6 -4
  97. data/lib/pdk/validate/yaml_validator.rb +0 -2
  98. data/lib/pdk/version.rb +1 -1
  99. data/locales/pdk.pot +634 -307
  100. metadata +100 -45
@@ -72,11 +72,6 @@ module PDK
72
72
  def target_type_spec_path
73
73
  @target_type_spec_path ||= File.join(module_dir, 'spec', 'unit', 'puppet', 'type', object_name) + '_spec.rb'
74
74
  end
75
-
76
- # transform a object name into a ruby class name
77
- def self.class_name_from_object_name(object_name)
78
- object_name.to_s.split('_').map(&:capitalize).join
79
- end
80
75
  end
81
76
  end
82
77
  end
@@ -4,6 +4,7 @@ module PDK
4
4
  module Generate
5
5
  class PuppetClass < PuppetObject
6
6
  OBJECT_TYPE = :class
7
+ PUPPET_STRINGS_TYPE = 'puppet_classes'.freeze
7
8
 
8
9
  # Prepares the data needed to render the new Puppet class template.
9
10
  #
@@ -1,12 +1,3 @@
1
- require 'fileutils'
2
-
3
- require 'pdk'
4
- require 'pdk/logger'
5
- require 'pdk/module/metadata'
6
- require 'pdk/module/templatedir'
7
- require 'pdk/template_file'
8
- require 'pdk/util/filesystem'
9
-
10
1
  module PDK
11
2
  module Generate
12
3
  class PuppetObject
@@ -29,7 +20,7 @@ module PDK
29
20
  # @param options [Hash{Symbol => Object}]
30
21
  #
31
22
  # @api public
32
- def initialize(module_dir, object_name, options = {})
23
+ def initialize(module_dir, object_name, options)
33
24
  @module_dir = module_dir
34
25
  @options = options
35
26
  @object_name = object_name
@@ -45,6 +36,10 @@ module PDK
45
36
  end
46
37
  end
47
38
 
39
+ def spec_only?
40
+ @options[:spec_only]
41
+ end
42
+
48
43
  # @abstract Subclass and implement {#template_data} to provide data to
49
44
  # the templates during rendering. Implementations of this method should
50
45
  # return a Hash!{Symbol => Object}.
@@ -81,6 +76,13 @@ module PDK
81
76
  nil
82
77
  end
83
78
 
79
+ # @abstract Subclass and implement {#target_device_path}. Implementations
80
+ # of this method should return a String containing the destination path
81
+ # of the device class being generated.
82
+ def target_device_path
83
+ nil
84
+ end
85
+
84
86
  # Retrieves the type of the object being generated, e.g. :class,
85
87
  # :defined_type, etc. This is specified in the subclass' OBJECT_TYPE
86
88
  # constant.
@@ -92,6 +94,37 @@ module PDK
92
94
  self.class::OBJECT_TYPE
93
95
  end
94
96
 
97
+ # Retrieves the type of the object being generated as represented in
98
+ # the JSON output of puppet-strings.
99
+ #
100
+ # @return [String] the type of the object being generated or nil if
101
+ # there is no mapping.
102
+ #
103
+ # @api private
104
+ def self.puppet_strings_type
105
+ return nil unless const_defined?(:PUPPET_STRINGS_TYPE)
106
+
107
+ self::PUPPET_STRINGS_TYPE
108
+ end
109
+
110
+ # Returns an array of possible target path strings.
111
+ def targets
112
+ targets = [
113
+ target_spec_path,
114
+ target_type_spec_path,
115
+ ]
116
+
117
+ unless spec_only?
118
+ targets += [
119
+ target_object_path,
120
+ target_type_path,
121
+ target_device_path,
122
+ ]
123
+ end
124
+
125
+ targets.compact
126
+ end
127
+
95
128
  # Check preconditions of this template group. By default this only makes sure that the target files do not
96
129
  # already exist. Override this (and call super) to add your own preconditions.
97
130
  #
@@ -99,16 +132,30 @@ module PDK
99
132
  #
100
133
  # @api public
101
134
  def check_preconditions
102
- [target_object_path, target_type_path, target_spec_path, target_type_spec_path].compact.each do |target_file|
103
- next unless File.exist?(target_file)
135
+ require 'pdk/util/filesystem'
136
+
137
+ targets.each do |target_file|
138
+ next unless PDK::Util::Filesystem.exist?(target_file)
104
139
 
105
140
  raise PDK::CLI::ExitWithError, _("Unable to generate %{object_type}; '%{file}' already exists.") % {
106
141
  file: target_file,
107
- object_type: object_type,
142
+ object_type: spec_only? ? 'unit test' : object_type,
108
143
  }
109
144
  end
110
145
  end
111
146
 
147
+ # Check the preconditions of this template group, behaving as a
148
+ # predicate rather than raising an exception.
149
+ #
150
+ # @return [Boolean] true if the generator is safe to run, otherwise
151
+ # false.
152
+ def can_run?
153
+ check_preconditions
154
+ true
155
+ rescue PDK::CLI::ExitWithError
156
+ false
157
+ end
158
+
112
159
  # Check that the templates can be rendered. Find an appropriate template
113
160
  # and create the target files from the template. This is the main entry
114
161
  # point for the class.
@@ -123,8 +170,9 @@ module PDK
123
170
  with_templates do |template_path, config_hash|
124
171
  data = template_data.merge(configs: config_hash)
125
172
 
126
- render_file(target_object_path, template_path[:object], data)
173
+ render_file(target_object_path, template_path[:object], data) unless spec_only?
127
174
  render_file(target_type_path, template_path[:type], data) if template_path[:type]
175
+ render_file(target_device_path, template_path[:device], data) if template_path[:device]
128
176
  render_file(target_spec_path, template_path[:spec], data) if template_path[:spec]
129
177
  render_file(target_type_spec_path, template_path[:type_spec], data) if template_path[:type_spec]
130
178
  end
@@ -148,6 +196,8 @@ module PDK
148
196
  #
149
197
  # @api private
150
198
  def render_file(dest_path, template_path, data)
199
+ require 'pdk/template_file'
200
+
151
201
  write_file(dest_path) do
152
202
  PDK::TemplateFile.new(template_path, data).render
153
203
  end
@@ -168,12 +218,15 @@ module PDK
168
218
  #
169
219
  # @api private
170
220
  def write_file(dest_path)
221
+ require 'pdk/logger'
222
+ require 'pdk/util/filesystem'
223
+
171
224
  PDK.logger.info(_("Creating '%{file}' from template.") % { file: dest_path })
172
225
 
173
226
  file_content = yield
174
227
 
175
228
  begin
176
- FileUtils.mkdir_p(File.dirname(dest_path))
229
+ PDK::Util::Filesystem.mkdir_p(File.dirname(dest_path))
177
230
  rescue SystemCallError => e
178
231
  raise PDK::CLI::FatalError, _("Unable to create directory '%{path}': %{message}") % {
179
232
  path: File.dirname(dest_path),
@@ -204,13 +257,17 @@ module PDK
204
257
  #
205
258
  # @api private
206
259
  def with_templates
260
+ require 'pdk/logger'
261
+ require 'pdk/module/templatedir'
262
+ require 'pdk/util/template_uri'
263
+
207
264
  templates.each do |template|
208
- if template[:url].nil?
209
- PDK.logger.debug(_('No %{dir_type} template specified; trying next template directory.') % { dir_type: template[:type] })
265
+ if template[:uri].nil?
266
+ PDK.logger.debug(_('No %{dir_type} template found; trying next template directory.') % { dir_type: template[:type] })
210
267
  next
211
268
  end
212
269
 
213
- PDK::Module::TemplateDir.new(template[:url]) do |template_dir|
270
+ PDK::Module::TemplateDir.new(PDK::Util::TemplateURI.new(template[:uri])) do |template_dir|
214
271
  template_paths = template_dir.object_template_for(object_type)
215
272
 
216
273
  if template_paths
@@ -219,9 +276,9 @@ module PDK
219
276
  # TODO: refactor to a search-and-execute form instead
220
277
  return # work is done # rubocop:disable Lint/NonLocalExitFromIterator
221
278
  elsif template[:allow_fallback]
222
- PDK.logger.debug(_('Unable to find a %{type} template in %{url}; trying next template directory.') % { type: object_type, url: template[:url] })
279
+ PDK.logger.debug(_('Unable to find a %{type} template in %{url}; trying next template directory.') % { type: object_type, url: template[:uri] })
223
280
  else
224
- raise PDK::CLI::FatalError, _('Unable to find the %{type} template in %{url}.') % { type: object_type, url: template[:url] }
281
+ raise PDK::CLI::FatalError, _('Unable to find the %{type} template in %{url}.') % { type: object_type, url: template[:uri] }
225
282
  end
226
283
  end
227
284
  end
@@ -250,38 +307,28 @@ module PDK
250
307
  #
251
308
  # @api private
252
309
  def templates
253
- @templates ||= [
254
- { type: 'CLI', url: @options[:'template-url'], allow_fallback: false },
255
- { type: 'metadata', url: module_metadata.data['template-url'], allow_fallback: true },
256
- { type: 'default', url: PDK::Util.default_template_url, allow_fallback: false },
257
- ]
310
+ require 'pdk/util/template_uri'
311
+
312
+ @templates ||= PDK::Util::TemplateURI.templates(@options)
258
313
  end
259
314
 
260
315
  # Retrieves the name of the module (without the forge username) from the
261
316
  # module metadata.
262
317
  #
263
- # @raise (see #module_metadata)
264
318
  # @return [String] The name of the module.
265
319
  #
266
320
  # @api private
267
321
  def module_name
268
- @module_name ||= module_metadata.data['name'].rpartition('-').last
322
+ require 'pdk/util'
323
+
324
+ @module_name ||= PDK::Util.module_metadata['name'].rpartition('-').last
325
+ rescue ArgumentError => e
326
+ raise PDK::CLI::FatalError, e
269
327
  end
270
328
 
271
- # Parses the metadata.json file for the module.
272
- #
273
- # @raise [PDK::CLI::FatalError] if the metadata.json file does not exist,
274
- # can not be read, or contains invalid metadata.
275
- #
276
- # @return [PDK::Module::Metadata] the parsed module metadata.
277
- #
278
- # @api private
279
- def module_metadata
280
- @module_metadata ||= begin
281
- PDK::Module::Metadata.from_file(File.join(module_dir, 'metadata.json'))
282
- rescue ArgumentError => e
283
- raise PDK::CLI::FatalError, _("'%{dir}' does not contain valid Puppet module metadata: %{msg}") % { dir: module_dir, msg: e.message }
284
- end
329
+ # transform a object name into a ruby class name
330
+ def self.class_name_from_object_name(object_name)
331
+ object_name.to_s.split('_').map(&:capitalize).join
285
332
  end
286
333
  end
287
334
  end
@@ -0,0 +1,87 @@
1
+ require 'pdk/generate/puppet_object'
2
+
3
+ module PDK
4
+ module Generate
5
+ class Transport < PuppetObject
6
+ OBJECT_TYPE = :transport
7
+
8
+ # Prepares the data needed to render the new defined type template.
9
+ #
10
+ # @return [Hash{Symbol => Object}] a hash of information that will be
11
+ # provided to the defined type and defined type spec templates during
12
+ # rendering.
13
+ def template_data
14
+ data = {
15
+ name: object_name,
16
+ transport_class: Transport.class_name_from_object_name(object_name),
17
+ }
18
+
19
+ data
20
+ end
21
+
22
+ def raise_precondition_error(error)
23
+ raise PDK::CLI::ExitWithError, _('%{error}: Creating a transport needs some local configuration in your module.' \
24
+ ' Please follow the docs at https://github.com/puppetlabs/puppet-resource_api#getting-started.') % { error: error }
25
+ end
26
+
27
+ def check_preconditions
28
+ super
29
+ # These preconditions can be removed once the pdk-templates are carrying the puppet-resource_api gem by default, and have switched
30
+ # the default mock_with value.
31
+ sync_path = PDK::Util.find_upwards('.sync.yml')
32
+ if sync_path.nil?
33
+ raise_precondition_error(_('.sync.yml not found'))
34
+ end
35
+ sync = YAML.load_file(sync_path)
36
+ if !sync.is_a? Hash
37
+ raise_precondition_error(_('.sync.yml contents is not a Hash'))
38
+ elsif !sync.key? 'Gemfile'
39
+ raise_precondition_error(_('Gemfile configuration not found'))
40
+ elsif !sync['Gemfile'].key? 'optional'
41
+ raise_precondition_error(_('Gemfile.optional configuration not found'))
42
+ elsif !sync['Gemfile']['optional'].key? ':development'
43
+ raise_precondition_error(_('Gemfile.optional.:development configuration not found'))
44
+ elsif sync['Gemfile']['optional'][':development'].none? { |g| g['gem'] == 'puppet-resource_api' }
45
+ raise_precondition_error(_('puppet-resource_api not found in the Gemfile config'))
46
+ elsif !sync.key? 'spec/spec_helper.rb'
47
+ raise_precondition_error(_('spec/spec_helper.rb configuration not found'))
48
+ elsif !sync['spec/spec_helper.rb'].key? 'mock_with'
49
+ raise_precondition_error(_('spec/spec_helper.rb.mock_with configuration not found'))
50
+ elsif !sync['spec/spec_helper.rb']['mock_with'] == ':rspec'
51
+ raise_precondition_error(_('spec/spec_helper.rb.mock_with not set to \':rspec\''))
52
+ end
53
+ end
54
+
55
+ # @return [String] the path where the new transport will be written.
56
+ def target_object_path
57
+ @target_object_path ||= File.join(module_dir, 'lib', 'puppet', 'transport', object_name) + '.rb'
58
+ end
59
+
60
+ # @return [String] the path where the new schema will be written.
61
+ def target_type_path
62
+ @target_type_path ||= File.join(module_dir, 'lib', 'puppet', 'transport', 'schema', object_name) + '.rb'
63
+ end
64
+
65
+ # @return [String] the path where the deviceshim for the transport will be written.
66
+ def target_device_path
67
+ @target_device_path ||= File.join(module_dir, 'lib', 'puppet', 'util', 'network_device', object_name, 'device.rb')
68
+ end
69
+
70
+ # @return [String] the path where the tests for the new transport
71
+ # will be written.
72
+ def target_spec_path
73
+ @target_spec_path ||= File.join(module_dir, 'spec', 'unit', 'puppet', 'transport', object_name) + '_spec.rb'
74
+ end
75
+
76
+ # @return [String] the path where the tests for the new schema will be written.
77
+ def target_type_spec_path
78
+ @target_type_spec_path ||= File.join(module_dir, 'spec', 'unit', 'puppet', 'transport', 'schema', object_name) + '_spec.rb'
79
+ end
80
+
81
+ # transform a object name into a ruby class name
82
+ def self.class_name_from_object_name(object_name)
83
+ object_name.to_s.split('_').map(&:capitalize).join
84
+ end
85
+ end
86
+ end
87
+ end
data/lib/pdk/logger.rb CHANGED
@@ -6,17 +6,37 @@ module PDK
6
6
  end
7
7
 
8
8
  class Logger < ::Logger
9
+ WRAP_COLUMN_LIMIT = 78
10
+
9
11
  def initialize
10
12
  super(STDERR)
13
+ @sent_messages = {}
11
14
 
12
15
  # TODO: Decide on output format.
13
16
  self.formatter = proc do |severity, _datetime, _progname, msg|
14
- "pdk (#{severity}): #{msg}\n"
17
+ prefix = "pdk (#{severity}): "
18
+ if msg.is_a?(Hash)
19
+ if msg.fetch(:wrap, false)
20
+ wrap_pattern = %r{(.{1,#{WRAP_COLUMN_LIMIT - prefix.length}})(\s+|\Z)}
21
+ "#{prefix}#{msg[:text].gsub(wrap_pattern, "\\1\n#{' ' * prefix.length}")}\n"
22
+ else
23
+ "#{prefix}#{msg[:text]}\n"
24
+ end
25
+ else
26
+ "#{prefix}#{msg}\n"
27
+ end
15
28
  end
16
29
 
17
30
  self.level = ::Logger::INFO
18
31
  end
19
32
 
33
+ def warn_once(*args)
34
+ hash = args.inspect.hash
35
+ return if (@sent_messages[::Logger::WARN] ||= {}).key?(hash)
36
+ @sent_messages[::Logger::WARN][hash] = true
37
+ warn(*args)
38
+ end
39
+
20
40
  def enable_debug_output
21
41
  self.level = ::Logger::DEBUG
22
42
  end
data/lib/pdk/module.rb CHANGED
@@ -1,5 +1,3 @@
1
- require 'pathspec'
2
-
3
1
  module PDK
4
2
  module Module
5
3
  DEFAULT_IGNORED = [
@@ -13,6 +11,8 @@ module PDK
13
11
  ].freeze
14
12
 
15
13
  def default_ignored_pathspec(ignore_dotfiles = true)
14
+ require 'pathspec'
15
+
16
16
  PathSpec.new(DEFAULT_IGNORED).tap do |ps|
17
17
  ps.add('.*') if ignore_dotfiles
18
18
  end
@@ -1,11 +1,3 @@
1
- require 'fileutils'
2
- require 'minitar'
3
- require 'zlib'
4
- require 'pathspec'
5
- require 'find'
6
- require 'pdk/module'
7
- require 'pdk/tests/unit'
8
-
9
1
  module PDK
10
2
  module Module
11
3
  class Build
@@ -26,6 +18,8 @@ module PDK
26
18
  #
27
19
  # @return [Hash{String => Object}] The hash of metadata values.
28
20
  def metadata
21
+ require 'pdk/module/metadata'
22
+
29
23
  @metadata ||= PDK::Module::Metadata.from_file(File.join(module_dir, 'metadata.json')).data
30
24
  end
31
25
 
@@ -71,6 +65,8 @@ module PDK
71
65
  #
72
66
  # If the directory already exists, remove it first.
73
67
  def create_build_dir
68
+ require 'fileutils'
69
+
74
70
  cleanup_build_dir
75
71
 
76
72
  FileUtils.mkdir_p(build_dir)
@@ -80,6 +76,8 @@ module PDK
80
76
  #
81
77
  # @return nil.
82
78
  def cleanup_build_dir
79
+ require 'fileutils'
80
+
83
81
  FileUtils.rm_rf(build_dir, secure: true)
84
82
  end
85
83
 
@@ -99,6 +97,8 @@ module PDK
99
97
  #
100
98
  # @return nil
101
99
  def stage_module_in_build_dir
100
+ require 'find'
101
+
102
102
  Find.find(module_dir) do |path|
103
103
  next if path == module_dir
104
104
 
@@ -112,6 +112,9 @@ module PDK
112
112
  #
113
113
  # @return nil.
114
114
  def stage_path(path)
115
+ require 'pathname'
116
+ require 'fileutils'
117
+
115
118
  relative_path = Pathname.new(path).relative_path_from(Pathname.new(module_dir))
116
119
  dest_path = File.join(build_dir, relative_path)
117
120
 
@@ -120,8 +123,14 @@ module PDK
120
123
  elsif File.symlink?(path)
121
124
  warn_symlink(path)
122
125
  else
126
+ validate_ustar_path!(relative_path.to_path)
123
127
  FileUtils.cp(path, dest_path, preserve: true)
124
128
  end
129
+ rescue ArgumentError => e
130
+ raise PDK::CLI::ExitWithError, _(
131
+ '%{message} Rename the file or exclude it from the package ' \
132
+ 'by adding it to the .pdkignore file in your module.',
133
+ ) % { message: e.message }
125
134
  end
126
135
 
127
136
  # Check if the given path matches one of the patterns listed in the
@@ -143,6 +152,8 @@ module PDK
143
152
  #
144
153
  # @return nil.
145
154
  def warn_symlink(path)
155
+ require 'pathname'
156
+
146
157
  symlink_path = Pathname.new(path)
147
158
  module_path = Pathname.new(module_dir)
148
159
 
@@ -152,6 +163,58 @@ module PDK
152
163
  }
153
164
  end
154
165
 
166
+ # Checks if the path length will fit into the POSIX.1-1998 (ustar) tar
167
+ # header format.
168
+ #
169
+ # POSIX.1-2001 (which allows paths of infinite length) was adopted by GNU
170
+ # tar in 2004 and is supported by minitar 0.7 and above. Unfortunately
171
+ # much of the Puppet ecosystem still uses minitar 0.6.1.
172
+ #
173
+ # POSIX.1-1998 tar format does not allow for paths greater than 256 bytes,
174
+ # or paths that can't be split into a prefix of 155 bytes (max) and
175
+ # a suffix of 100 bytes (max).
176
+ #
177
+ # This logic was pretty much copied from the private method
178
+ # {Archive::Tar::Minitar::Writer#split_name}.
179
+ #
180
+ # @param path [String] the relative path to be added to the tar file.
181
+ #
182
+ # @raise [ArgumentError] if the path is too long or could not be split.
183
+ #
184
+ # @return [nil]
185
+ def validate_ustar_path!(path)
186
+ if path.bytesize > 256
187
+ raise ArgumentError, _("The path '%{path}' is longer than 256 bytes.") % {
188
+ path: path,
189
+ }
190
+ end
191
+
192
+ if path.bytesize <= 100
193
+ prefix = ''
194
+ else
195
+ parts = path.split(File::SEPARATOR)
196
+ newpath = parts.pop
197
+ nxt = ''
198
+
199
+ loop do
200
+ nxt = parts.pop || ''
201
+ break if newpath.bytesize + 1 + nxt.bytesize >= 100
202
+ newpath = File.join(nxt, newpath)
203
+ end
204
+
205
+ prefix = File.join(*parts, nxt)
206
+ path = newpath
207
+ end
208
+
209
+ return unless path.bytesize > 100 || prefix.bytesize > 155
210
+
211
+ raise ArgumentError, _(
212
+ "'%{path}' could not be split at a directory separator into two " \
213
+ 'parts, the first having a maximum length of 155 bytes and the ' \
214
+ 'second having a maximum length of 100 bytes.',
215
+ ) % { path: path }
216
+ end
217
+
155
218
  # Creates a gzip compressed tarball of the build directory.
156
219
  #
157
220
  # If the destination package already exists, it will be removed before
@@ -159,11 +222,38 @@ module PDK
159
222
  #
160
223
  # @return nil.
161
224
  def build_package
225
+ require 'fileutils'
226
+ require 'zlib'
227
+ require 'minitar'
228
+ require 'find'
229
+
162
230
  FileUtils.rm_f(package_file)
163
231
 
164
232
  Dir.chdir(target_dir) do
165
- Zlib::GzipWriter.open(package_file) do |package_fd|
166
- Minitar.pack(release_name, package_fd)
233
+ begin
234
+ gz = Zlib::GzipWriter.new(File.open(package_file, 'wb'))
235
+ tar = Minitar::Output.new(gz)
236
+ Find.find(release_name) do |entry|
237
+ entry_meta = {
238
+ name: entry,
239
+ }
240
+
241
+ orig_mode = File.stat(entry).mode
242
+ min_mode = Minitar.dir?(entry) ? 0o755 : 0o644
243
+
244
+ entry_meta[:mode] = orig_mode | min_mode
245
+
246
+ if entry_meta[:mode] != orig_mode
247
+ PDK.logger.debug(_('Updated permissions of packaged \'%{entry}\' to %{new_mode}') % {
248
+ entry: entry,
249
+ new_mode: (entry_meta[:mode] & 0o7777).to_s(8),
250
+ })
251
+ end
252
+
253
+ Minitar.pack_file(entry_meta, tar)
254
+ end
255
+ ensure
256
+ tar.close
167
257
  end
168
258
  end
169
259
  end
@@ -188,6 +278,9 @@ module PDK
188
278
  #
189
279
  # @return [PathSpec] The populated ignore path matcher.
190
280
  def ignored_files
281
+ require 'pdk/module'
282
+ require 'pathspec'
283
+
191
284
  @ignored_files ||=
192
285
  begin
193
286
  ignored = if ignore_file.nil?