pdk 1.9.0 → 3.2.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 (163) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +744 -711
  3. data/README.md +23 -21
  4. data/lib/pdk/answer_file.rb +3 -112
  5. data/lib/pdk/bolt.rb +20 -0
  6. data/lib/pdk/cli/build.rb +51 -54
  7. data/lib/pdk/cli/bundle.rb +33 -29
  8. data/lib/pdk/cli/console.rb +148 -0
  9. data/lib/pdk/cli/convert.rb +46 -37
  10. data/lib/pdk/cli/env.rb +51 -0
  11. data/lib/pdk/cli/errors.rb +4 -3
  12. data/lib/pdk/cli/exec/command.rb +285 -0
  13. data/lib/pdk/cli/exec/interactive_command.rb +109 -0
  14. data/lib/pdk/cli/exec.rb +32 -201
  15. data/lib/pdk/cli/exec_group.rb +79 -43
  16. data/lib/pdk/cli/get/config.rb +26 -0
  17. data/lib/pdk/cli/get.rb +22 -0
  18. data/lib/pdk/cli/new/class.rb +20 -22
  19. data/lib/pdk/cli/new/defined_type.rb +21 -21
  20. data/lib/pdk/cli/new/fact.rb +27 -0
  21. data/lib/pdk/cli/new/function.rb +27 -0
  22. data/lib/pdk/cli/new/module.rb +40 -29
  23. data/lib/pdk/cli/new/provider.rb +18 -18
  24. data/lib/pdk/cli/new/task.rb +23 -22
  25. data/lib/pdk/cli/new/test.rb +52 -0
  26. data/lib/pdk/cli/new/transport.rb +27 -0
  27. data/lib/pdk/cli/new.rb +15 -9
  28. data/lib/pdk/cli/release/prep.rb +39 -0
  29. data/lib/pdk/cli/release/publish.rb +46 -0
  30. data/lib/pdk/cli/release.rb +185 -0
  31. data/lib/pdk/cli/remove/config.rb +83 -0
  32. data/lib/pdk/cli/remove.rb +22 -0
  33. data/lib/pdk/cli/set/config.rb +121 -0
  34. data/lib/pdk/cli/set.rb +22 -0
  35. data/lib/pdk/cli/test/unit.rb +71 -69
  36. data/lib/pdk/cli/test.rb +9 -8
  37. data/lib/pdk/cli/update.rb +38 -21
  38. data/lib/pdk/cli/util/command_redirector.rb +13 -3
  39. data/lib/pdk/cli/util/interview.rb +25 -9
  40. data/lib/pdk/cli/util/option_normalizer.rb +6 -6
  41. data/lib/pdk/cli/util/option_validator.rb +19 -9
  42. data/lib/pdk/cli/util/spinner.rb +13 -0
  43. data/lib/pdk/cli/util/update_manager_printer.rb +82 -0
  44. data/lib/pdk/cli/util.rb +105 -48
  45. data/lib/pdk/cli/validate.rb +96 -111
  46. data/lib/pdk/cli.rb +134 -87
  47. data/lib/pdk/config/errors.rb +5 -0
  48. data/lib/pdk/config/ini_file.rb +184 -0
  49. data/lib/pdk/config/ini_file_setting.rb +35 -0
  50. data/lib/pdk/config/json.rb +35 -0
  51. data/lib/pdk/config/json_schema_namespace.rb +137 -0
  52. data/lib/pdk/config/json_schema_setting.rb +51 -0
  53. data/lib/pdk/config/json_with_schema.rb +47 -0
  54. data/lib/pdk/config/namespace.rb +362 -0
  55. data/lib/pdk/config/setting.rb +134 -0
  56. data/lib/pdk/config/task_schema.json +116 -0
  57. data/lib/pdk/config/validator.rb +31 -0
  58. data/lib/pdk/config/yaml.rb +41 -0
  59. data/lib/pdk/config/yaml_with_schema.rb +51 -0
  60. data/lib/pdk/config.rb +304 -0
  61. data/lib/pdk/context/control_repo.rb +61 -0
  62. data/lib/pdk/context/module.rb +28 -0
  63. data/lib/pdk/context/none.rb +22 -0
  64. data/lib/pdk/context.rb +98 -0
  65. data/lib/pdk/control_repo.rb +89 -0
  66. data/lib/pdk/generate/defined_type.rb +27 -33
  67. data/lib/pdk/generate/fact.rb +26 -0
  68. data/lib/pdk/generate/function.rb +49 -0
  69. data/lib/pdk/generate/module.rb +160 -153
  70. data/lib/pdk/generate/provider.rb +16 -69
  71. data/lib/pdk/generate/puppet_class.rb +27 -32
  72. data/lib/pdk/generate/puppet_object.rb +100 -159
  73. data/lib/pdk/generate/task.rb +34 -51
  74. data/lib/pdk/generate/transport.rb +34 -0
  75. data/lib/pdk/generate.rb +21 -8
  76. data/lib/pdk/logger.rb +24 -6
  77. data/lib/pdk/module/build.rb +125 -37
  78. data/lib/pdk/module/convert.rb +146 -65
  79. data/lib/pdk/module/metadata.rb +72 -71
  80. data/lib/pdk/module/release.rb +255 -0
  81. data/lib/pdk/module/update.rb +48 -37
  82. data/lib/pdk/module/update_manager.rb +75 -39
  83. data/lib/pdk/module.rb +10 -2
  84. data/lib/pdk/monkey_patches.rb +268 -0
  85. data/lib/pdk/report/event.rb +36 -48
  86. data/lib/pdk/report.rb +35 -22
  87. data/lib/pdk/template/fetcher/git.rb +84 -0
  88. data/lib/pdk/template/fetcher/local.rb +29 -0
  89. data/lib/pdk/template/fetcher.rb +100 -0
  90. data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +108 -0
  91. data/lib/pdk/template/renderer/v1/renderer.rb +131 -0
  92. data/lib/pdk/template/renderer/v1/template_file.rb +100 -0
  93. data/lib/pdk/template/renderer/v1.rb +25 -0
  94. data/lib/pdk/template/renderer.rb +97 -0
  95. data/lib/pdk/template/template_dir.rb +67 -0
  96. data/lib/pdk/template.rb +52 -0
  97. data/lib/pdk/tests/unit.rb +101 -51
  98. data/lib/pdk/util/bundler.rb +44 -42
  99. data/lib/pdk/util/changelog_generator.rb +138 -0
  100. data/lib/pdk/util/env.rb +48 -0
  101. data/lib/pdk/util/filesystem.rb +139 -2
  102. data/lib/pdk/util/git.rb +108 -8
  103. data/lib/pdk/util/json_finder.rb +86 -0
  104. data/lib/pdk/util/puppet_strings.rb +125 -0
  105. data/lib/pdk/util/puppet_version.rb +71 -87
  106. data/lib/pdk/util/ruby_version.rb +49 -25
  107. data/lib/pdk/util/template_uri.rb +283 -0
  108. data/lib/pdk/util/vendored_file.rb +34 -42
  109. data/lib/pdk/util/version.rb +11 -10
  110. data/lib/pdk/util/windows/api_types.rb +74 -44
  111. data/lib/pdk/util/windows/file.rb +31 -27
  112. data/lib/pdk/util/windows/process.rb +74 -0
  113. data/lib/pdk/util/windows/string.rb +19 -12
  114. data/lib/pdk/util/windows.rb +2 -0
  115. data/lib/pdk/util.rb +111 -124
  116. data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -0
  117. data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -0
  118. data/lib/pdk/validate/external_command_validator.rb +213 -0
  119. data/lib/pdk/validate/internal_ruby_validator.rb +101 -0
  120. data/lib/pdk/validate/invokable_validator.rb +238 -0
  121. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +84 -0
  122. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +76 -0
  123. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
  124. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +131 -0
  125. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
  126. data/lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb +38 -0
  127. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +135 -0
  128. data/lib/pdk/validate/puppet/puppet_validator_group.rb +22 -0
  129. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +79 -0
  130. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
  131. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +83 -0
  132. data/lib/pdk/validate/tasks/tasks_name_validator.rb +45 -0
  133. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
  134. data/lib/pdk/validate/validator.rb +120 -0
  135. data/lib/pdk/validate/validator_group.rb +107 -0
  136. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +86 -0
  137. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
  138. data/lib/pdk/validate.rb +86 -12
  139. data/lib/pdk/version.rb +2 -2
  140. data/lib/pdk.rb +60 -10
  141. metadata +138 -100
  142. data/lib/pdk/cli/module/build.rb +0 -14
  143. data/lib/pdk/cli/module/generate.rb +0 -45
  144. data/lib/pdk/cli/module.rb +0 -14
  145. data/lib/pdk/i18n.rb +0 -4
  146. data/lib/pdk/module/templatedir.rb +0 -321
  147. data/lib/pdk/template_file.rb +0 -95
  148. data/lib/pdk/validate/base_validator.rb +0 -215
  149. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -86
  150. data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -109
  151. data/lib/pdk/validate/metadata_validator.rb +0 -30
  152. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -67
  153. data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -112
  154. data/lib/pdk/validate/puppet_validator.rb +0 -30
  155. data/lib/pdk/validate/ruby/rubocop.rb +0 -77
  156. data/lib/pdk/validate/ruby_validator.rb +0 -29
  157. data/lib/pdk/validate/tasks/metadata_lint.rb +0 -126
  158. data/lib/pdk/validate/tasks/name.rb +0 -88
  159. data/lib/pdk/validate/tasks_validator.rb +0 -33
  160. data/lib/pdk/validate/yaml/syntax.rb +0 -109
  161. data/lib/pdk/validate/yaml_validator.rb +0 -31
  162. data/locales/config.yaml +0 -21
  163. data/locales/pdk.pot +0 -1291
@@ -0,0 +1,116 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-06/schema#",
3
+ "title": "Puppet Task Metadata",
4
+ "description": "The metadata format for Puppet Tasks",
5
+ "type": "object",
6
+ "properties": {
7
+ "description": {
8
+ "type": "string",
9
+ "description": "A longer description(one paragraph) of how to use the task"
10
+ },
11
+ "puppet_task_version": {
12
+ "type": "integer",
13
+ "description": "The version of this spec used",
14
+ "default": 1
15
+ },
16
+ "supports_noop": {
17
+ "type": "boolean",
18
+ "default": false,
19
+ "description": "This task respects the '_noop' metaparam. If this false or absent the task runner will refuse to run this task if noop is specified."
20
+ },
21
+ "remote": {
22
+ "type": "boolean",
23
+ "default": false,
24
+ "description": "This task is capable of operating on a remote target using connection information in the '_target' metaparam."
25
+ },
26
+ "input_method": {
27
+ "type": "string",
28
+ "enum": ["stdin", "environment", "both", "powershell"],
29
+ "description": "What input method should be used to pass params to the task"
30
+ },
31
+ "parameters": {
32
+ "$ref": "#/definitions/paramsObject",
33
+ "description": "An object mapping valid parameter names to corresponding json-schemas"
34
+ },
35
+ "implementations": {
36
+ "type": "array",
37
+ "items": {
38
+ "type": "object",
39
+ "required": ["name"],
40
+ "properties": {
41
+ "name": {
42
+ "type": "string",
43
+ "description": "Name of task executable file"
44
+ },
45
+ "requirements": {
46
+ "type": "array",
47
+ "additionalItems": {
48
+ "type": "string"
49
+ },
50
+ "description": "Features required on target to execute task"
51
+ },
52
+ "files": {
53
+ "type": "array",
54
+ "additionalItems": {
55
+ "type": "string"
56
+ },
57
+ "description": "File resources required by task"
58
+ }
59
+ }
60
+ },
61
+ "description": "Rules for selecting implementation resources based on features available on target"
62
+ },
63
+ "files": {
64
+ "type": "array",
65
+ "items": {
66
+ "type": "string"
67
+ },
68
+ "description": "Path to file resources saved in valid module directory to be provided to task"
69
+ },
70
+ "private": {
71
+ "type": "boolean",
72
+ "description": "Should this task appear by default in UI lists of tasks"
73
+ },
74
+ "extensions": {
75
+ "type": "object",
76
+ "description": "Task Runner specific metadata extensions",
77
+ "items": {
78
+ "type": "object"
79
+ }
80
+ }
81
+ },
82
+ "definitions": {
83
+ "parameterName": {
84
+ "description": "Valid names for parameter keys",
85
+ "type": "string",
86
+ "pattern": "^[a-z][a-z0-9_]*$"
87
+ },
88
+ "paramsObject": {
89
+ "type": "object",
90
+ "description": "An object with restricted keys and enumData support",
91
+ "propertyNames": {"$ref": "#/definitions/parameterName"},
92
+ "additionalProperties": {
93
+ "type": "object",
94
+ "description": "Extend Normal JSON schema to require an object and describe 'enumData' to map enum values to descriptions",
95
+ "properties": {
96
+ "description": {
97
+ "description": "A description of the parameter",
98
+ "type": "string"
99
+ },
100
+ "type": {
101
+ "description": "A puppet type string that describes a data type that will match the parameter value",
102
+ "type": "string"
103
+ },
104
+ "sensitive": {
105
+ "description": "Whether the task runner should treat the parameter value as sensitive",
106
+ "type": "boolean",
107
+ "default": false
108
+ },
109
+ "default": {
110
+ "description": "The default value to pass to the task implementation if the parameter isn't provided"
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,31 @@
1
+ module PDK
2
+ class Config
3
+ # A collection of predefined validators for use with {PDK::Config::Value}.
4
+ #
5
+ # @example
6
+ # value :enabled do
7
+ # validate PDK::Config::Validator.boolean
8
+ # end
9
+ module Validator
10
+ # @return [Hash{Symbol => [Proc,String]}] a {PDK::Config::Value}
11
+ # validator that ensures that the value is either a TrueClass or
12
+ # FalseClass.
13
+ def self.boolean
14
+ {
15
+ proc: ->(value) { [true, false].include?(value) },
16
+ message: 'must be a boolean: true or false'
17
+ }
18
+ end
19
+
20
+ # @return [Hash{Symbol => [Proc,String]}] a {PDK::Config::Value}
21
+ # validator that ensures that the value is a String that matches the
22
+ # regex for a version 4 UUID.
23
+ def self.uuid
24
+ {
25
+ proc: ->(value) { value.match(/\A\h{8}(?:-\h{4}){3}-\h{12}\z/) },
26
+ message: 'must be a version 4 UUID'
27
+ }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,41 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Config
5
+ # Parses a YAML document.
6
+ #
7
+ # @see PDK::Config::Namespace.parse_file
8
+ class YAML < Namespace
9
+ def parse_file(filename)
10
+ raise unless block_given?
11
+
12
+ data = load_data(filename)
13
+ return if data.nil? || data.empty?
14
+
15
+ require 'yaml'
16
+
17
+ data = if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1')
18
+ ::YAML.safe_load(data, permitted_classes: [Symbol], permitted_symbols: [], aliases: true)
19
+ else
20
+ ::YAML.safe_load(data, [Symbol], [], true)
21
+ end
22
+ return if data.nil?
23
+
24
+ data.each { |k, v| yield k, PDK::Config::Setting.new(k, self, v) }
25
+ rescue Psych::SyntaxError => e
26
+ raise PDK::Config::LoadError, format('Syntax error when loading %{file}: %{error}', file: filename, error: "#{e.problem} #{e.context}")
27
+ rescue Psych::DisallowedClass => e
28
+ raise PDK::Config::LoadError, format('Unsupported class in %{file}: %{error}', file: filename, error: e.message)
29
+ end
30
+
31
+ # Serializes object data into a YAML string.
32
+ #
33
+ # @see PDK::Config::Namespace.serialize_data
34
+ def serialize_data(data)
35
+ require 'yaml'
36
+
37
+ ::YAML.dump(data)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Config
5
+ # Parses a YAML document with a JSON schema.
6
+ #
7
+ # @see PDK::Config::Namespace.parse_file
8
+ class YAMLWithSchema < JSONSchemaNamespace
9
+ def parse_file(filename)
10
+ raise unless block_given?
11
+
12
+ data = load_data(filename)
13
+ data = '' if data.nil?
14
+ require 'yaml'
15
+ require 'json-schema'
16
+
17
+ @raw_data = ::YAML.safe_load(data, [Symbol], [], true)
18
+ @raw_data = {} if @raw_data.nil?
19
+
20
+ begin
21
+ # Ensure the parsed document is actually valid
22
+ validate_document!(@raw_data)
23
+ rescue ::JSON::Schema::ValidationError => e
24
+ raise PDK::Config::LoadError, format('The configuration file %{filename} is not valid: %{message}', filename: filename, message: e.message)
25
+ end
26
+
27
+ require 'pdk/config/json_schema_setting'
28
+
29
+ schema_property_names.each do |key|
30
+ yield key, PDK::Config::JSONSchemaSetting.new(key, self, @raw_data[key])
31
+ end
32
+
33
+ # Remove all of the "known" settings from the schema and
34
+ # we're left with the settings that we don't manage.
35
+ self.unmanaged_settings = @raw_data.reject { |k, _| schema_property_names.include?(k) }
36
+ rescue Psych::SyntaxError => e
37
+ raise PDK::Config::LoadError, format('Syntax error when loading %{file}: %{error}', file: filename, error: "#{e.problem} #{e.context}")
38
+ rescue Psych::DisallowedClass => e
39
+ raise PDK::Config::LoadError, format('Unsupported class in %{file}: %{error}', file: filename, error: e.message)
40
+ end
41
+
42
+ # Serializes object data into a YAML string.
43
+ #
44
+ # @see PDK::Config::Namespace.serialize_data
45
+ def serialize_data(data)
46
+ require 'yaml'
47
+ ::YAML.dump(data)
48
+ end
49
+ end
50
+ end
51
+ end
data/lib/pdk/config.rb ADDED
@@ -0,0 +1,304 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Config
5
+ autoload :IniFile, 'pdk/config/ini_file'
6
+ autoload :IniFileSetting, 'pdk/config/ini_file_setting'
7
+ autoload :JSON, 'pdk/config/json'
8
+ autoload :JSONSchemaNamespace, 'pdk/config/json_schema_namespace'
9
+ autoload :JSONSchemaSetting, 'pdk/config/json_schema_setting'
10
+ autoload :LoadError, 'pdk/config/errors'
11
+ autoload :Namespace, 'pdk/config/namespace'
12
+ autoload :Setting, 'pdk/config/setting'
13
+ autoload :Validator, 'pdk/config/validator'
14
+ autoload :YAML, 'pdk/config/yaml'
15
+
16
+ # Create a new instance of the PDK Configuration
17
+ # @param options [Hash[String => Object]] Optional hash to override configuration options
18
+ # @option options [String] 'system.path' Path to the system PDK configuration file
19
+ # @option options [String] 'system.module_defaults.path' Path to the system module answers PDK configuration file
20
+ # @option options [String] 'user.path' Path to the user PDK configuration file
21
+ # @option options [String] 'user.module_defaults.path' Path to the user module answers PDK configuration file
22
+ # @option options [PDK::Context::AbstractContext] 'context' The context that the configuration should be created in
23
+ def initialize(options = nil)
24
+ options = {} if options.nil?
25
+ @config_options = {
26
+ 'system.path' => PDK::Config.system_config_path,
27
+ 'system.module_defaults.path' => PDK::Config.system_answers_path,
28
+ 'user.path' => PDK::Config.user_config_path,
29
+ 'user.module_defaults.path' => PDK::AnswerFile.default_answer_file_path,
30
+ 'context' => PDK.context
31
+ }.merge(options)
32
+ end
33
+
34
+ # The system level configuration settings.
35
+ # @return [PDK::Config::Namespace]
36
+ # @api private
37
+ def system_config
38
+ local_options = @config_options
39
+ @system_config ||= PDK::Config::JSON.new('system', file: local_options['system.path']) do
40
+ mount :module_defaults, PDK::Config::JSON.new(file: local_options['system.module_defaults.path'])
41
+ end
42
+ end
43
+
44
+ # The user level configuration settings.
45
+ # @return [PDK::Config::Namespace]
46
+ # @api private
47
+ def user_config
48
+ local_options = @config_options
49
+ @user_config ||= PDK::Config::JSON.new('user', file: local_options['user.path']) do
50
+ mount :module_defaults, PDK::Config::JSON.new(file: local_options['user.module_defaults.path'])
51
+
52
+ # Display the feature flags
53
+ mount :pdk_feature_flags, PDK::Config::Namespace.new('pdk_feature_flags') do
54
+ setting 'available' do
55
+ default_to { PDK.available_feature_flags }
56
+ end
57
+
58
+ setting 'requested' do
59
+ default_to { PDK.requested_feature_flags }
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ # The project level configuration settings.
66
+ # @return [PDK::Config::Namespace]
67
+ # @api private
68
+ def project_config
69
+ context = @config_options['context']
70
+ @project ||= PDK::Config::Namespace.new('project') do
71
+ mount :environment, PDK::ControlRepo.environment_conf_as_config(File.join(context.root_path, 'environment.conf')) if context.is_a?(PDK::Context::ControlRepo)
72
+
73
+ mount :validate, PDK::Config::YAML.new('validate', file: File.join(context.root_path, 'pdk.yaml'), persistent_defaults: true) do
74
+ setting 'ignore' do
75
+ default_to { [] }
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ # Resolves *all* filtered settings from all namespaces
82
+ #
83
+ # @param filter [String] Only resolve setting names which match the filter. See PDK::Config::Namespace.be_resolved? for matching rules
84
+ # @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
85
+ def resolve(filter = nil)
86
+ all_scopes.values.reverse.reduce({}) do |result, method_name|
87
+ result.merge(send(method_name).resolve(filter))
88
+ end
89
+ end
90
+
91
+ # Returns a configuration setting by name. This name can either be a String, Array or parameters e.g. These are equivalent
92
+ # - PDK.config.get('user.a.b.c')
93
+ # - PDK.config.get(['user', 'a', 'b', 'c'])
94
+ # - PDK.config.get('user', 'a', 'b', 'c')
95
+ # @param root [Array[String], String] The root setting name or the entire setting name as a single string
96
+ # @param keys [String] The child names of the setting
97
+ # @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
98
+ def get(root, *keys)
99
+ return nil if root.nil? || root.empty?
100
+
101
+ if keys.empty?
102
+ case root
103
+ when Array
104
+ name = root
105
+ when String
106
+ name = split_key_string(root)
107
+ else
108
+ return nil
109
+ end
110
+ else
111
+ name = [root].concat(keys)
112
+ end
113
+
114
+ get_within_scopes(name[1..], [name[0]])
115
+ end
116
+
117
+ # Returns a configuration setting by name, using scope precedence rules. If no scopes are passed, then all scopes are queried using the default precedence rules
118
+ # @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
119
+ # @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
120
+ # @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
121
+ def get_within_scopes(setting_name, scopes = nil)
122
+ raise ArgumentError, format('Expected an Array but got \'%{klass}\' for scopes', klass: scopes.class) unless scopes.nil? || scopes.is_a?(Array)
123
+ raise ArgumentError, format('Expected an Array or String but got \'%{klass}\' for setting_name', klass: setting_name.class) unless setting_name.is_a?(Array) || setting_name.is_a?(String)
124
+
125
+ setting_arr = setting_name.is_a?(String) ? split_key_string(setting_name) : setting_name
126
+ all_scope_names = all_scopes.keys
127
+
128
+ # Use only valid scope names
129
+ scopes = scopes.nil? ? all_scope_names : scopes & all_scope_names
130
+
131
+ scopes.each do |scope_name|
132
+ value = traverse_object(send(all_scopes[scope_name]), *setting_arr)
133
+ return value unless value.nil?
134
+ end
135
+ nil
136
+ end
137
+
138
+ # Yields a configuration setting value by name, using scope precedence rules. If no scopes are passed, then all scopes are queried using the default precedence rules
139
+ # @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
140
+ # @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
141
+ # @yield [PDK::Config::Namespace, Object] The value of the configuration setting. Does not yield if the setting does not exist or is nil
142
+ def with_scoped_value(setting_name, scopes = nil)
143
+ raise ArgumentError, 'must be passed a block' unless block_given?
144
+
145
+ value = get_within_scopes(setting_name, scopes)
146
+ yield value unless value.nil?
147
+ end
148
+
149
+ # Sets a configuration setting by name. This name can either be a String or an Array
150
+ # - PDK.config.set('user.a.b.c', ...)
151
+ # - PDK.config.set(['user', 'a', 'b', 'c'], ...)
152
+ # @param key [String, Array[String]] The name of the configuration key to change
153
+ # @param value [Object] The value to set the configuration setting to
154
+ # @param options [Hash] Changes the behaviour of the setting process
155
+ # @option options [Boolean] :force Disables any munging or array processing, and sets the value as it is. Default is false
156
+ # @return [Object] The new value of the configuration setting
157
+ def set(key, value, options = {})
158
+ options = {
159
+ force: false
160
+ }.merge(options)
161
+
162
+ names = key.is_a?(String) ? split_key_string(key) : key
163
+ raise ArgumentError, 'Invalid configuration names' if names.nil? || !names.is_a?(Array) || names.empty?
164
+
165
+ scope_name = names[0]
166
+ raise ArgumentError, format("Unknown configuration root '%{name}'", name: scope_name) if all_scopes[scope_name].nil?
167
+
168
+ deep_set_object(value, options[:force], send(all_scopes[scope_name]), *names[1..])
169
+ end
170
+
171
+ def self.user_config_path
172
+ File.join(PDK::Util.configdir, 'user_config.json')
173
+ end
174
+
175
+ def self.system_config_path
176
+ File.join(PDK::Util.system_configdir, 'system_config.json')
177
+ end
178
+
179
+ def self.system_answers_path
180
+ File.join(PDK::Util.system_configdir, 'answers.json')
181
+ end
182
+
183
+ def self.json_schemas_path
184
+ File.join(__dir__, 'config')
185
+ end
186
+
187
+ # return nil if not exist
188
+ def self.json_schema(name)
189
+ File.join(json_schemas_path, "#{name}_schema.json")
190
+ end
191
+
192
+ private
193
+
194
+ # :nocov: This is a private method and is tested elsewhere
195
+ def traverse_object(object, *names)
196
+ return nil if object.nil? || !object.respond_to?(:[])
197
+ return nil if names.nil?
198
+ # It's possible to pass in empty names at the root traversal layer
199
+ # but this should _only_ happen at the root namespace level
200
+ if names.empty?
201
+ return (object.is_a?(PDK::Config::Namespace) ? object : nil)
202
+ end
203
+
204
+ name = names.shift
205
+ value = object[name]
206
+ if names.empty?
207
+ return value if value.is_a?(PDK::Config::Namespace)
208
+
209
+ # Duplicate arrays and hashes so that they are isolated from changes being made
210
+ value.is_a?(Hash) || value.is_a?(Array) ? value.dup : value
211
+ else
212
+ traverse_object(value, *names)
213
+ end
214
+ end
215
+ # :nocov:
216
+
217
+ # :nocov: This is a private method and is tested elsewhere
218
+ # Takes a string representation of a setting and splits into its constituent setting parts e.g.
219
+ # 'user.a.b.c' becomes ['user', 'a', 'b', 'c']
220
+ # @return [Array[String]] The string split into each setting name as an array
221
+ def split_key_string(key)
222
+ raise ArgumentError, format('Expected a String but got \'%{klass}\'', klass: key.class) unless key.is_a?(String)
223
+
224
+ key.split('.')
225
+ end
226
+ # :nocov:
227
+
228
+ # :nocov:
229
+ # Returns all known scope names and their associated method name to call, to query the scope
230
+ # Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
231
+ # @return [Hash[String, Symbol]] A hash of the scope name then method name to call to query the scope (as a Symbol)
232
+ def all_scopes
233
+ # Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
234
+ {
235
+ 'project' => :project_config,
236
+ 'user' => :user_config,
237
+ 'system' => :system_config
238
+ }.freeze
239
+ end
240
+
241
+ # :nocov: This is a private method and is tested elsewhere
242
+ # Deeply traverses an object tree via `[]` and sets the last
243
+ # element to the value specified.
244
+ #
245
+ # Creating any missing parent hashes during the traversal
246
+ def deep_set_object(value, force, namespace, *names)
247
+ raise ArgumentError, 'Missing or invalid namespace' unless namespace.is_a?(PDK::Config::Namespace)
248
+ raise ArgumentError, 'Missing a name to set' if names.nil? || names.empty?
249
+
250
+ name = names.shift
251
+ current_value = namespace[name]
252
+
253
+ # If the next thing in the traversal chain is another namespace, set the value using that child namespace.
254
+ return deep_set_object(value, force, current_value, *names) if current_value.is_a?(PDK::Config::Namespace)
255
+
256
+ # We're at the end of the name traversal
257
+ if names.empty?
258
+ if force || !current_value.is_a?(Array)
259
+ namespace[name] = value
260
+ return value
261
+ end
262
+
263
+ # Arrays are a special case if we're not forcing the value
264
+ namespace[name] = current_value << value unless current_value.include?(value)
265
+ return value
266
+ end
267
+
268
+ # Need to generate a deep hash using the current remaining names
269
+ # So given an origin *names of ['a', 'b', 'c', 'd'] and a value 'foo',
270
+ # we eventually want a hash of `{"b"=>{"c"=>{"d"=>"foo"}}}`
271
+ #
272
+ # The code above has already shifted the first element so we currently have
273
+ # name : 'a'
274
+ # names: ['b', 'c', 'd']
275
+ #
276
+ #
277
+ # First we need to pop off the last element ('d') in this case as we need to set that in the `reduce` call below
278
+ # So now we have:
279
+ # name : 'a'
280
+ # names: ['b', 'c']
281
+ # last_name : 'd'
282
+ last_name = names.pop
283
+ # Using reduce and an accumulator, we create the nested hash from the deepest value first. In this case the deepest value
284
+ # is the last_name, so the starting condition is {"d"=>"foo"}
285
+ # After the first iteration ('c'), the accumulator has {"c"=>{"d"=>"foo"}}}
286
+ # After the last iteration ('b'), the accumulator has {"b"=>{"c"=>{"d"=>"foo"}}}
287
+ hash_value = names.reverse.reduce(last_name => value) { |accumulator, item| { item => accumulator } }
288
+
289
+ # If the current value is nil, then it can't be a namespace or an existing value
290
+ # or
291
+ # If the current value is not a Hash and are forcing the change.
292
+ if current_value.nil? || (force && !current_value.is_a?(Hash))
293
+ namespace[name] = hash_value
294
+ return value
295
+ end
296
+
297
+ raise ArgumentError, format("Unable to set '%{key}' to '%{value}' as it is not a Hash", key: "#{namespace.name}.#{name}", value: hash_value) unless current_value.is_a?(Hash)
298
+
299
+ namespace[name] = current_value.merge(hash_value)
300
+ value
301
+ end
302
+ # :nocov:
303
+ end
304
+ end
@@ -0,0 +1,61 @@
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
+
30
+ value = environment_conf['modulepath'] || ''
31
+ # We have to use a hardcoded value here because File::PATH_SEPARATOR is ';' on Windows.
32
+ # As the environment.conf is only used on Puppet Server, it's always ':'
33
+ # Based on - https://github.com/puppetlabs/puppet/blob/f3e6d7e6d87f46408943a8e2176afb82ff6ea096/lib/puppet/settings/environment_conf.rb#L98-L106
34
+ @module_paths = value.split(':')
35
+ end
36
+
37
+ # The relative module_paths that exist on disk.
38
+ # @see https://puppet.com/docs/puppet/latest/config_file_environment.html#allowed-settings
39
+ # @return [Array[String]] The relative module paths on disk
40
+ def actualized_module_paths
41
+ @actualized_module_paths ||= module_paths.reject { |path| path.start_with?('$') }
42
+ .select { |path| PDK::Util::Filesystem.directory?(PDK::Util::Filesystem.expand_path(File.join(root_path, path))) }
43
+ end
44
+
45
+ # :nocov:
46
+ # @see PDK::Context::AbstractContext.display_name
47
+ def display_name
48
+ 'a Control Repository context'
49
+ end
50
+ # :nocov:
51
+
52
+ private
53
+
54
+ # Memoization helper to read the environment.conf file.
55
+ # @return [PDK::Config::IniFile]
56
+ def environment_conf
57
+ @environment_conf ||= PDK::ControlRepo.environment_conf_as_config(File.join(root_path, 'environment.conf'))
58
+ end
59
+ end
60
+ end
61
+ 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