pdk 1.16.0 → 1.17.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 (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/lib/pdk.rb +25 -18
  4. data/lib/pdk/answer_file.rb +2 -93
  5. data/lib/pdk/cli.rb +1 -5
  6. data/lib/pdk/cli/config.rb +3 -1
  7. data/lib/pdk/cli/config/get.rb +3 -1
  8. data/lib/pdk/cli/convert.rb +1 -1
  9. data/lib/pdk/cli/exec/command.rb +13 -0
  10. data/lib/pdk/cli/exec_group.rb +78 -43
  11. data/lib/pdk/cli/get.rb +20 -0
  12. data/lib/pdk/cli/get/config.rb +24 -0
  13. data/lib/pdk/cli/util.rb +6 -3
  14. data/lib/pdk/cli/validate.rb +26 -44
  15. data/lib/pdk/config.rb +178 -4
  16. data/lib/pdk/config/ini_file.rb +183 -0
  17. data/lib/pdk/config/ini_file_setting.rb +39 -0
  18. data/lib/pdk/config/namespace.rb +25 -5
  19. data/lib/pdk/config/setting.rb +3 -2
  20. data/lib/pdk/context.rb +96 -0
  21. data/lib/pdk/context/control_repo.rb +60 -0
  22. data/lib/pdk/context/module.rb +28 -0
  23. data/lib/pdk/context/none.rb +22 -0
  24. data/lib/pdk/control_repo.rb +40 -0
  25. data/lib/pdk/generate/module.rb +8 -12
  26. data/lib/pdk/module/release.rb +2 -8
  27. data/lib/pdk/util.rb +35 -5
  28. data/lib/pdk/util/bundler.rb +1 -0
  29. data/lib/pdk/util/changelog_generator.rb +6 -1
  30. data/lib/pdk/util/template_uri.rb +4 -3
  31. data/lib/pdk/validate.rb +72 -25
  32. data/lib/pdk/validate/external_command_validator.rb +208 -0
  33. data/lib/pdk/validate/internal_ruby_validator.rb +100 -0
  34. data/lib/pdk/validate/invokable_validator.rb +216 -0
  35. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +86 -0
  36. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +78 -0
  37. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
  38. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +133 -0
  39. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
  40. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +137 -0
  41. data/lib/pdk/validate/puppet/puppet_validator_group.rb +21 -0
  42. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +80 -0
  43. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
  44. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +88 -0
  45. data/lib/pdk/validate/tasks/tasks_name_validator.rb +50 -0
  46. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
  47. data/lib/pdk/validate/validator.rb +111 -0
  48. data/lib/pdk/validate/validator_group.rb +103 -0
  49. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +95 -0
  50. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
  51. data/lib/pdk/version.rb +1 -1
  52. data/locales/pdk.pot +161 -125
  53. metadata +29 -17
  54. data/lib/pdk/validate/base_validator.rb +0 -215
  55. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -82
  56. data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -111
  57. data/lib/pdk/validate/metadata_validator.rb +0 -26
  58. data/lib/pdk/validate/puppet/puppet_epp.rb +0 -135
  59. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -64
  60. data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -135
  61. data/lib/pdk/validate/puppet_validator.rb +0 -26
  62. data/lib/pdk/validate/ruby/rubocop.rb +0 -72
  63. data/lib/pdk/validate/ruby_validator.rb +0 -26
  64. data/lib/pdk/validate/tasks/metadata_lint.rb +0 -130
  65. data/lib/pdk/validate/tasks/name.rb +0 -90
  66. data/lib/pdk/validate/tasks_validator.rb +0 -29
  67. data/lib/pdk/validate/yaml/syntax.rb +0 -125
  68. data/lib/pdk/validate/yaml_validator.rb +0 -28
@@ -0,0 +1,39 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Config
5
+ class IniFileSetting < PDK::Config::Setting
6
+ # Initialises the PDK::Config::JSONSchemaSetting object.
7
+ #
8
+ # @see PDK::Config::Setting.initialize
9
+ def initialize(_name, namespace, initial_value = nil)
10
+ raise 'The IniFileSetting object can only be created within the IniFile Namespace' unless namespace.is_a?(PDK::Config::IniFile)
11
+ super
12
+ validate!(initial_value) unless initial_value.nil?
13
+ end
14
+
15
+ # Verifies that the new setting value is valid in an Ini File
16
+ #
17
+ # @see PDK::Config::Setting.validate!
18
+ def validate!(value)
19
+ # We're very restrictive here. Realistically Ini files only have string types
20
+ return if value.nil? || value.is_a?(String) || value.is_a?(Integer)
21
+ # The only other valid-ish type is a Hash
22
+ unless value.is_a?(Hash)
23
+ raise ArgumentError, _('The setting %{key} may only be a String or Integer, not %{class}') % {
24
+ key: qualified_name,
25
+ class: value.class,
26
+ }
27
+ end
28
+ # Any hashes can only have a single String/Integer value
29
+ value.each do |child_name, child_value|
30
+ next if child_value.nil? || child_value.is_a?(String) || child_value.is_a?(Integer)
31
+ raise ArgumentError, _('The setting %{key} may only be a String or Integer, not %{class}') % {
32
+ key: qualified_name + '.' + child_name,
33
+ class: child_value.class,
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -34,6 +34,7 @@ module PDK
34
34
  @persistent_defaults = persistent_defaults
35
35
  @mounts = {}
36
36
  @loaded_from_file = false
37
+ @read_only = false
37
38
 
38
39
  instance_eval(&block) if block_given?
39
40
  end
@@ -48,7 +49,7 @@ module PDK
48
49
  #
49
50
  # @return [nil]
50
51
  def setting(key, &block)
51
- @settings[key.to_s] ||= PDK::Config::Setting.new(key.to_s, self)
52
+ @settings[key.to_s] ||= default_setting_class.new(key.to_s, self)
52
53
  @settings[key.to_s].instance_eval(&block) if block_given?
53
54
  end
54
55
 
@@ -97,7 +98,8 @@ module PDK
97
98
  # Check if it's a setting, otherwise nil
98
99
  return nil if settings[key.to_s].nil?
99
100
  return settings[key.to_s].value unless settings[key.to_s].value.nil?
100
- default_value = settings[key.to_s].default
101
+ # Duplicate arrays and hashes so that they are isolated from changes being made
102
+ default_value = PDK::Util.deep_duplicate(settings[key.to_s].default)
101
103
  return default_value if default_value.nil? || !@persistent_defaults
102
104
  # Persist the default value
103
105
  settings[key.to_s].value = default_value
@@ -205,8 +207,26 @@ module PDK
205
207
  child_namespace? && file.nil?
206
208
  end
207
209
 
210
+ # Disables the namespace, and child namespaces, from writing changes to disk.
211
+ # Typically this is only needed for unit testing.
212
+ # @api private
213
+ def read_only!
214
+ @read_only = true
215
+ @mounts.each { |_, child| child.read_only! }
216
+ end
217
+
208
218
  private
209
219
 
220
+ # Returns the object class to create settings with. Subclasses may override this to use specific setting classes
221
+ #
222
+ # @return [Class[PDK::Config::Setting]]
223
+ #
224
+ # @abstract
225
+ # @private
226
+ def default_setting_class
227
+ PDK::Config::Setting
228
+ end
229
+
210
230
  # Determines whether a setting name should be resolved using the filter
211
231
  # Returns true when filter is nil.
212
232
  # Returns true if the filter is exactly the same name as the setting.
@@ -253,7 +273,7 @@ module PDK
253
273
  # Need to use `@settings` and `@mounts` here to stop recursive calls
254
274
  return unless @mounts[key.to_s].nil?
255
275
  return unless @settings[key.to_s].nil?
256
- @settings[key.to_s] = PDK::Config::Setting.new(key.to_s, self, initial_value)
276
+ @settings[key.to_s] = default_setting_class.new(key.to_s, self, initial_value)
257
277
  end
258
278
 
259
279
  # Set the value of the named key.
@@ -281,7 +301,7 @@ module PDK
281
301
  return if filename.nil?
282
302
  return unless PDK::Util::Filesystem.file?(filename)
283
303
 
284
- PDK::Util::Filesystem.read_file(file)
304
+ PDK::Util::Filesystem.read_file(filename)
285
305
  rescue Errno::ENOENT => e
286
306
  raise PDK::Config::LoadError, e.message
287
307
  rescue Errno::EACCES
@@ -302,7 +322,7 @@ module PDK
302
322
  #
303
323
  # @return [nil]
304
324
  def save_data
305
- return if file.nil?
325
+ return if file.nil? || @read_only
306
326
 
307
327
  PDK::Util::Filesystem.mkdir_p(File.dirname(file))
308
328
 
@@ -46,8 +46,9 @@ module PDK
46
46
  [namespace.name, @name].join('.')
47
47
  end
48
48
 
49
- def value # rubocop:disable Style/TrivialAccessors
50
- @value
49
+ def value
50
+ # Duplicate arrays and hashes so that they are isolated from changes being made
51
+ PDK::Util.deep_duplicate(@value)
51
52
  end
52
53
 
53
54
  def value=(obj)
@@ -0,0 +1,96 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Context
5
+ autoload :None, 'pdk/context/none'
6
+ autoload :Module, 'pdk/context/module'
7
+ autoload :ControlRepo, 'pdk/context/control_repo'
8
+
9
+ # Automatically determines the PDK Context given a path. Create will continue up the directory tree until it
10
+ # finds a valid context
11
+ # @return [PDK::Context::AbstractContext] Returns a PDK::Context::None if the context could not be determined
12
+ def self.create(context_path)
13
+ return PDK::Context::None.new(context_path) unless PDK::Util::Filesystem.directory?(context_path)
14
+
15
+ previous = nil
16
+ current = PDK::Util::Filesystem.expand_path(context_path)
17
+ until !PDK::Util::Filesystem.directory?(current) || current == previous
18
+ # Control Repo detection
19
+ return PDK::Context::ControlRepo.new(current, context_path) if PDK.feature_flag?('controlrepo') && PDK::ControlRepo.control_repo_root?(current)
20
+
21
+ # Puppet Module detection
22
+ metadata_file = File.join(current, 'metadata.json')
23
+ if PDK::Util::Filesystem.file?(metadata_file) || PDK::Util.in_module_root?(context_path)
24
+ return PDK::Context::Module.new(current, context_path)
25
+ end
26
+
27
+ previous = current
28
+ current = PDK::Util::Filesystem.expand_path('..', current)
29
+ end
30
+ PDK::Context::None.new(context_path)
31
+ end
32
+
33
+ # Abstract class which all PDK Contexts will subclass from.
34
+ # @abstract
35
+ class AbstractContext
36
+ # The root of this context, for example the module root when inside a module. This is different from context_path
37
+ # For example a Module context_path could be /path/to/module/manifests/ but the root_path will be /path/to/module as
38
+ # that is the root of the Module context
39
+ # @return [String, Nil]
40
+ attr_reader :root_path
41
+
42
+ # The path used to create this context, for example the current working directory. This is different from root_path
43
+ # For example a Module context_path could be /path/to/module/manifests/ but the root_path will be /path/to/module as
44
+ # that is the root of the Module context
45
+ # @return [String]
46
+ attr_reader :context_path
47
+
48
+ # @param context_path [String] The path where this context was created from e.g. Dir.pwd
49
+ def initialize(context_path)
50
+ @context_path = context_path
51
+ end
52
+
53
+ # Whether the current context is compatible with the PDK e.g. in a Module context, whether it has the correct metadata.json content
54
+ # @return [Boolean] Default is not compatible
55
+ def pdk_compatible?
56
+ false
57
+ end
58
+
59
+ # The friendly name to display for this context
60
+ # @api private
61
+ # @abstract
62
+ def display_name; end
63
+
64
+ # The context which this context is in. For example a Module Context (/controlrepo/site/profile) can be inside of a Control Repo context (/controlrepo)
65
+ # The default is to search in the parent directory of this context
66
+ # @return [PDK::Context::AbstractContext, Nil] Returns the parent context or nil if there is no parent.
67
+ def parent_context
68
+ # Default detection is just look for the context in the parent directory of this context
69
+ @parent_context || PDK::Context.create(File.dirname(root_path))
70
+ end
71
+
72
+ # Writes the current context information, and parent contexts, to the PDK Debug Logger.
73
+ # This is mainly used by the PDK CLI when in debug mode to assist users to figure out why the PDK is misbehaving.
74
+ # @api private
75
+ def to_debug_log
76
+ current = self
77
+ depth = 1
78
+ loop do
79
+ PDK.logger.debug("Detected #{current.display_name} at #{current.root_path.nil? ? current.context_path : current.root_path}")
80
+ current = current.parent_context
81
+ break if current.nil?
82
+ depth += 1
83
+ # Circuit breaker in case there are circular references
84
+ break if depth > 20
85
+ end
86
+ nil
87
+ end
88
+
89
+ #:nocov: There's nothing to test here
90
+ def to_s
91
+ "#<#{self.class}:#{object_id}>#{context_path}"
92
+ end
93
+ #:nocov:
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,60 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Context
5
+ # Represents a context for a directory based Control Repository
6
+ class ControlRepo < PDK::Context::AbstractContext
7
+ # @param repo_root [String] The root path for the control repo.
8
+ # @param context_path [String] The path where this context was created from e.g. Dir.pwd
9
+ # @see PDK::Context::AbstractContext
10
+ def initialize(repo_root, context_path)
11
+ super(context_path)
12
+ @root_path = repo_root
13
+ @environment_conf = nil
14
+ end
15
+
16
+ # @see PDK::Context::AbstractContext.pdk_compatible?
17
+ def pdk_compatible?
18
+ # Currently there is nothing to determine compatibility with the PDK for a
19
+ # Control Repo. For now assume everything is compatible
20
+ true
21
+ end
22
+
23
+ # The modulepath setting for this control repository as an array of strings. These paths are relative
24
+ # and may contain interpolation strings (e.g. $basemodulepath)
25
+ # @see https://puppet.com/docs/puppet/latest/config_file_environment.html#allowed-settings
26
+ # @return [Array[String]] The modulepath setting for this control repository
27
+ def module_paths
28
+ return @module_paths unless @module_paths.nil?
29
+ value = environment_conf['modulepath'] || ''
30
+ # We have to use a hardcoded value here because File::PATH_SEPARATOR is ';' on Windows.
31
+ # As the environment.conf is only used on Puppet Server, it's always ':'
32
+ # Based on - https://github.com/puppetlabs/puppet/blob/f3e6d7e6d87f46408943a8e2176afb82ff6ea096/lib/puppet/settings/environment_conf.rb#L98-L106
33
+ @module_paths = value.split(':')
34
+ end
35
+
36
+ # The relative module_paths that exist on disk.
37
+ # @see https://puppet.com/docs/puppet/latest/config_file_environment.html#allowed-settings
38
+ # @return [Array[String]] The relative module paths on disk
39
+ def actualized_module_paths
40
+ @actualized_module_paths ||= module_paths.reject { |path| path.start_with?('$') }
41
+ .select { |path| PDK::Util::Filesystem.directory?(PDK::Util::Filesystem.expand_path(File.join(root_path, path))) }
42
+ end
43
+
44
+ #:nocov:
45
+ # @see PDK::Context::AbstractContext.display_name
46
+ def display_name
47
+ _('a Control Repository context')
48
+ end
49
+ #:nocov:
50
+
51
+ private
52
+
53
+ # Memoization helper to read the environment.conf file.
54
+ # @return [PDK::Config::IniFile]
55
+ def environment_conf
56
+ @environment_conf ||= PDK::ControlRepo.environment_conf_as_config(File.join(root_path, 'environment.conf'))
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,28 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Context
5
+ # Represents a context for a Puppet Module
6
+ class Module < PDK::Context::AbstractContext
7
+ # @param module_root [String] The root path for the module.
8
+ # @param context_path [String] The path where this context was created from e.g. Dir.pwd
9
+ # @see PDK::Context::AbstractContext
10
+ def initialize(module_root, context_path)
11
+ super(context_path)
12
+ @root_path = module_root
13
+ end
14
+
15
+ # @see PDK::Context::AbstractContext.pdk_compatible?
16
+ def pdk_compatible?
17
+ PDK::Util.module_pdk_compatible?(root_path)
18
+ end
19
+
20
+ #:nocov:
21
+ # @see PDK::Context::AbstractContext.display_name
22
+ def display_name
23
+ _('a Puppet Module context')
24
+ end
25
+ #:nocov:
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Context
5
+ # Represents a context which the PDK does not know. For example
6
+ # an empty directory
7
+ class None < PDK::Context::AbstractContext
8
+ #:nocov:
9
+ # @see PDK::Context::AbstractContext.display_name
10
+ def display_name
11
+ _('an unknown context')
12
+ end
13
+ #:nocov:
14
+
15
+ # @see PDK::Context::AbstractContext.parent_context
16
+ def parent_context
17
+ # An unknown context has no parent
18
+ nil
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,6 +4,16 @@ module PDK
4
4
  module ControlRepo
5
5
  CONTROL_REPO_FILES = %w[environment.conf Puppetfile].freeze
6
6
 
7
+ DEFAULT_IGNORED = [
8
+ '/pkg/',
9
+ '~*',
10
+ '/coverage',
11
+ # Strictly speaking this isn't default but if people have tricked older PDK into thinking that a
12
+ # Control Repo is a module, they may have recursive symlinks in spec/fixtures/modules
13
+ '/spec/fixtures/modules/',
14
+ '/vendor/',
15
+ ].freeze
16
+
7
17
  # Returns path to the root of the Control Repo being worked on.
8
18
  #
9
19
  # An environment.conf is required for a PDK compatible Control Repo,
@@ -46,5 +56,35 @@ module PDK
46
56
  CONTROL_REPO_FILES.any? { |file| PDK::Util::Filesystem.file?(File.join(path, file)) }
47
57
  end
48
58
  module_function :control_repo_root?
59
+
60
+ # Returns a PDK::Config::Namespace for the specified environment.conf file.
61
+ # Note there is no validation of the path.
62
+ #
63
+ # @param path [String] The path to the environment.conf file
64
+ #
65
+ # @return [PDK::Config::IniFile] The configuration file
66
+ def environment_conf_as_config(path)
67
+ PDK::Config::IniFile.new('environment', file: path) do
68
+ setting :modulepath do
69
+ # As per https://puppet.com/docs/puppet/latest/config_file_environment.html#allowed-settings
70
+ default_to { 'modules:$basemodulepath' }
71
+ end
72
+
73
+ setting :manifest do
74
+ # As per https://puppet.com/docs/puppet/latest/config_file_environment.html#allowed-settings
75
+ default_to { 'manifests/' }
76
+ end
77
+ end
78
+ end
79
+ module_function :environment_conf_as_config
80
+
81
+ def default_ignored_pathspec(ignore_dotfiles = true)
82
+ require 'pathspec'
83
+
84
+ PathSpec.new(DEFAULT_IGNORED).tap do |ps|
85
+ ps.add('.*') if ignore_dotfiles
86
+ end
87
+ end
88
+ module_function :default_ignored_pathspec
49
89
  end
50
90
  end
@@ -82,11 +82,11 @@ module PDK
82
82
  # If the user specifies our default template url via the command
83
83
  # line, remove the saved template-url answer so that the template_uri
84
84
  # resolution can find new default URLs in the future.
85
- PDK.answers.update!('template-url' => nil) if opts.key?(:'template-url')
85
+ PDK.config.user['module_defaults']['template-url'] = nil if opts.key?(:'template-url')
86
86
  else
87
87
  # Save the template-url answers if the module was generated using a
88
88
  # template/reference other than ours.
89
- PDK.answers.update!('template-url' => template_uri.metadata_format)
89
+ PDK.config.user['module_defaults']['template-url'] = template_uri.metadata_format
90
90
  end
91
91
 
92
92
  begin
@@ -135,13 +135,13 @@ module PDK
135
135
  require 'pdk/answer_file'
136
136
  require 'pdk/module/metadata'
137
137
 
138
- opts[:username] = (opts[:username] || PDK.answers['forge_username'] || username_from_login).downcase
138
+ opts[:username] = (opts[:username] || PDK.config.get_within_scopes('module_defaults.forge_username') || username_from_login).downcase
139
139
 
140
140
  defaults = PDK::Module::Metadata::DEFAULTS.dup
141
141
 
142
142
  defaults['name'] = "#{opts[:username]}-#{opts[:module_name]}" unless opts[:module_name].nil?
143
- defaults['author'] = PDK.answers['author'] unless PDK.answers['author'].nil?
144
- defaults['license'] = PDK.answers['license'] unless PDK.answers['license'].nil?
143
+ PDK.config.with_scoped_value('module_defaults.author') { |val| defaults['author'] = val }
144
+ PDK.config.with_scoped_value('module_defaults.license') { |val| defaults['license'] = val }
145
145
  defaults['license'] = opts[:license] if opts.key?(:license)
146
146
 
147
147
  metadata = PDK::Module::Metadata.new(defaults)
@@ -342,13 +342,9 @@ module PDK
342
342
  end
343
343
 
344
344
  require 'pdk/answer_file'
345
- PDK.answers.update!(
346
- {
347
- 'forge_username' => opts[:username],
348
- 'author' => answers['author'],
349
- 'license' => answers['license'],
350
- }.delete_if { |_key, value| value.nil? },
351
- )
345
+ PDK.config.user['module_defaults']['forge_username'] = opts[:username] unless opts[:username].nil?
346
+ PDK.config.user['module_defaults']['author'] = answers['author'] unless answers['author'].nil?
347
+ PDK.config.user['module_defaults']['license'] = answers['license'] unless answers['license'].nil?
352
348
  end
353
349
  end
354
350
  end
@@ -105,20 +105,14 @@ module PDK
105
105
 
106
106
  PDK::CLI::Util.module_version_check
107
107
 
108
- report = PDK::Report.new
109
108
  puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts)
110
109
  PDK::Util::PuppetVersion.fetch_puppet_dev if opts[:'puppet-dev']
111
110
  PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
112
111
 
113
- opts = opts.merge(puppet_env[:gemset])
114
-
115
112
  PDK::Util::Bundler.ensure_bundle!(puppet_env[:gemset])
116
113
 
117
- validators = PDK::Validate.validators
118
- validators.each do |validator|
119
- validator_exit_code = validator.invoke(report, opts.dup)
120
- raise PDK::CLI::ExitWithError, _('An error occured during validation') unless validator_exit_code.zero?
121
- end
114
+ validator_exit_code, = PDK::Validate.invoke_validators_by_name(PDK::Validate.validator_names, false, options)
115
+ raise PDK::CLI::ExitWithError, _('An error occured during validation') unless validator_exit_code.zero?
122
116
  end
123
117
 
124
118
  def run_documentation(_opts)