pdk 2.3.0 → 2.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1329 -1321
- data/LICENSE +201 -201
- data/README.md +163 -163
- data/exe/pdk +10 -10
- data/lib/pdk/analytics/client/google_analytics.rb +143 -143
- data/lib/pdk/analytics/client/noop.rb +25 -25
- data/lib/pdk/analytics/util.rb +19 -19
- data/lib/pdk/analytics.rb +30 -30
- data/lib/pdk/answer_file.rb +12 -12
- data/lib/pdk/bolt.rb +19 -19
- data/lib/pdk/cli/build.rb +82 -82
- data/lib/pdk/cli/bundle.rb +48 -48
- data/lib/pdk/cli/config/get.rb +26 -26
- data/lib/pdk/cli/config.rb +22 -22
- data/lib/pdk/cli/console.rb +148 -148
- data/lib/pdk/cli/convert.rb +52 -52
- data/lib/pdk/cli/env.rb +52 -52
- data/lib/pdk/cli/errors.rb +25 -25
- data/lib/pdk/cli/exec/command.rb +293 -293
- data/lib/pdk/cli/exec/interactive_command.rb +114 -114
- data/lib/pdk/cli/exec.rb +84 -84
- data/lib/pdk/cli/exec_group.rb +104 -104
- data/lib/pdk/cli/get/config.rb +24 -24
- data/lib/pdk/cli/get.rb +20 -20
- data/lib/pdk/cli/module/build.rb +12 -12
- data/lib/pdk/cli/module/generate.rb +47 -47
- data/lib/pdk/cli/module.rb +14 -14
- data/lib/pdk/cli/new/class.rb +32 -32
- data/lib/pdk/cli/new/defined_type.rb +32 -32
- data/lib/pdk/cli/new/fact.rb +29 -29
- data/lib/pdk/cli/new/function.rb +29 -29
- data/lib/pdk/cli/new/module.rb +53 -53
- data/lib/pdk/cli/new/provider.rb +29 -29
- data/lib/pdk/cli/new/task.rb +34 -34
- data/lib/pdk/cli/new/test.rb +52 -52
- data/lib/pdk/cli/new/transport.rb +27 -27
- data/lib/pdk/cli/new.rb +21 -21
- data/lib/pdk/cli/release/prep.rb +39 -39
- data/lib/pdk/cli/release/publish.rb +50 -50
- data/lib/pdk/cli/release.rb +194 -194
- data/lib/pdk/cli/remove/config.rb +80 -80
- data/lib/pdk/cli/remove.rb +20 -20
- data/lib/pdk/cli/set/config.rb +119 -119
- data/lib/pdk/cli/set.rb +20 -20
- data/lib/pdk/cli/test/unit.rb +90 -90
- data/lib/pdk/cli/test.rb +11 -11
- data/lib/pdk/cli/update.rb +64 -64
- data/lib/pdk/cli/util/command_redirector.rb +27 -27
- data/lib/pdk/cli/util/interview.rb +72 -72
- data/lib/pdk/cli/util/option_normalizer.rb +55 -55
- data/lib/pdk/cli/util/option_validator.rb +68 -68
- data/lib/pdk/cli/util/spinner.rb +13 -13
- data/lib/pdk/cli/util/update_manager_printer.rb +82 -82
- data/lib/pdk/cli/util.rb +305 -305
- data/lib/pdk/cli/validate.rb +116 -116
- data/lib/pdk/cli.rb +175 -175
- data/lib/pdk/config/analytics_schema.json +26 -26
- data/lib/pdk/config/errors.rb +5 -5
- data/lib/pdk/config/ini_file.rb +183 -183
- data/lib/pdk/config/ini_file_setting.rb +39 -39
- data/lib/pdk/config/json.rb +34 -34
- data/lib/pdk/config/json_schema_namespace.rb +142 -142
- data/lib/pdk/config/json_schema_setting.rb +53 -53
- data/lib/pdk/config/json_with_schema.rb +49 -49
- data/lib/pdk/config/namespace.rb +354 -354
- data/lib/pdk/config/setting.rb +135 -135
- data/lib/pdk/config/validator.rb +31 -31
- data/lib/pdk/config/yaml.rb +46 -46
- data/lib/pdk/config/yaml_with_schema.rb +59 -59
- data/lib/pdk/config.rb +390 -390
- data/lib/pdk/context/control_repo.rb +60 -60
- data/lib/pdk/context/module.rb +28 -28
- data/lib/pdk/context/none.rb +22 -22
- data/lib/pdk/context.rb +99 -99
- data/lib/pdk/control_repo.rb +90 -90
- data/lib/pdk/generate/defined_type.rb +43 -43
- data/lib/pdk/generate/fact.rb +25 -25
- data/lib/pdk/generate/function.rb +48 -48
- data/lib/pdk/generate/module.rb +352 -352
- data/lib/pdk/generate/provider.rb +28 -28
- data/lib/pdk/generate/puppet_class.rb +43 -43
- data/lib/pdk/generate/puppet_object.rb +232 -232
- data/lib/pdk/generate/task.rb +68 -68
- data/lib/pdk/generate/transport.rb +33 -33
- data/lib/pdk/generate.rb +24 -24
- data/lib/pdk/i18n.rb +4 -4
- data/lib/pdk/logger.rb +45 -45
- data/lib/pdk/module/build.rb +322 -322
- data/lib/pdk/module/convert.rb +296 -296
- data/lib/pdk/module/metadata.rb +202 -202
- data/lib/pdk/module/release.rb +260 -260
- data/lib/pdk/module/update.rb +131 -131
- data/lib/pdk/module/update_manager.rb +227 -227
- data/lib/pdk/module.rb +30 -30
- data/lib/pdk/report/event.rb +370 -370
- data/lib/pdk/report.rb +121 -121
- data/lib/pdk/template/fetcher/git.rb +85 -85
- data/lib/pdk/template/fetcher/local.rb +28 -28
- data/lib/pdk/template/fetcher.rb +98 -98
- data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +116 -116
- data/lib/pdk/template/renderer/v1/renderer.rb +132 -132
- data/lib/pdk/template/renderer/v1/template_file.rb +102 -102
- data/lib/pdk/template/renderer/v1.rb +25 -25
- data/lib/pdk/template/renderer.rb +96 -96
- data/lib/pdk/template/template_dir.rb +67 -67
- data/lib/pdk/template.rb +59 -59
- data/lib/pdk/tests/unit.rb +252 -252
- data/lib/pdk/util/bundler.rb +259 -259
- data/lib/pdk/util/changelog_generator.rb +137 -137
- data/lib/pdk/util/env.rb +47 -47
- data/lib/pdk/util/filesystem.rb +138 -138
- data/lib/pdk/util/git.rb +179 -179
- data/lib/pdk/util/json_finder.rb +85 -85
- data/lib/pdk/util/puppet_strings.rb +125 -125
- data/lib/pdk/util/puppet_version.rb +266 -266
- data/lib/pdk/util/ruby_version.rb +179 -179
- data/lib/pdk/util/template_uri.rb +295 -295
- data/lib/pdk/util/vendored_file.rb +93 -93
- data/lib/pdk/util/version.rb +43 -43
- data/lib/pdk/util/windows/api_types.rb +82 -82
- data/lib/pdk/util/windows/file.rb +36 -36
- data/lib/pdk/util/windows/process.rb +79 -79
- data/lib/pdk/util/windows/string.rb +16 -16
- data/lib/pdk/util/windows.rb +15 -15
- data/lib/pdk/util.rb +278 -277
- data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -23
- data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -98
- data/lib/pdk/validate/external_command_validator.rb +208 -208
- data/lib/pdk/validate/internal_ruby_validator.rb +100 -100
- data/lib/pdk/validate/invokable_validator.rb +228 -228
- data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +86 -86
- data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +78 -78
- data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -20
- data/lib/pdk/validate/puppet/puppet_epp_validator.rb +133 -133
- data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -66
- data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +137 -137
- data/lib/pdk/validate/puppet/puppet_validator_group.rb +21 -21
- data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +80 -80
- data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -19
- data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +88 -88
- data/lib/pdk/validate/tasks/tasks_name_validator.rb +50 -50
- data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -20
- data/lib/pdk/validate/validator.rb +118 -118
- data/lib/pdk/validate/validator_group.rb +104 -104
- data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +95 -95
- data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -19
- data/lib/pdk/validate.rb +94 -94
- data/lib/pdk/version.rb +4 -4
- data/lib/pdk.rb +76 -76
- data/locales/config.yaml +21 -21
- data/locales/pdk.pot +2094 -2094
- metadata +5 -6
data/lib/pdk/config.rb
CHANGED
|
@@ -1,390 +1,390 @@
|
|
|
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 [String] 'user.analytics.path' Path to the user analytics PDK configuration file
|
|
23
|
-
# @option options [PDK::Context::AbstractContext] 'context' The context that the configuration should be created in
|
|
24
|
-
def initialize(options = nil)
|
|
25
|
-
options = {} if options.nil?
|
|
26
|
-
@config_options = {
|
|
27
|
-
'system.path' => PDK::Config.system_config_path,
|
|
28
|
-
'system.module_defaults.path' => PDK::Config.system_answers_path,
|
|
29
|
-
'user.path' => PDK::Config.user_config_path,
|
|
30
|
-
'user.module_defaults.path' => PDK::AnswerFile.default_answer_file_path,
|
|
31
|
-
'user.analytics.path' => PDK::Config.analytics_config_path,
|
|
32
|
-
'context' => PDK.context,
|
|
33
|
-
}.merge(options)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# The system level configuration settings.
|
|
37
|
-
# @return [PDK::Config::Namespace]
|
|
38
|
-
# @api private
|
|
39
|
-
def system_config
|
|
40
|
-
local_options = @config_options
|
|
41
|
-
@system_config ||= PDK::Config::JSON.new('system', file: local_options['system.path']) do
|
|
42
|
-
mount :module_defaults, PDK::Config::JSON.new(file: local_options['system.module_defaults.path'])
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# The user level configuration settings.
|
|
47
|
-
# @return [PDK::Config::Namespace]
|
|
48
|
-
# @api private
|
|
49
|
-
def user_config
|
|
50
|
-
local_options = @config_options
|
|
51
|
-
@user_config ||= PDK::Config::JSON.new('user', file: local_options['user.path']) do
|
|
52
|
-
mount :module_defaults, PDK::Config::JSON.new(file: local_options['user.module_defaults.path'])
|
|
53
|
-
|
|
54
|
-
# Due to the json-schema gem having issues with Windows based paths, and only supporting Draft 05 (or less) do
|
|
55
|
-
# not use JSON validation yet. Once PDK drops support for EOL rubies, we will be able to use the json_schemer gem
|
|
56
|
-
# Which has much more modern support
|
|
57
|
-
# Reference - https://github.com/puppetlabs/pdk/pull/777
|
|
58
|
-
# Reference - https://tickets.puppetlabs.com/browse/PDK-1526
|
|
59
|
-
mount :analytics, PDK::Config::YAML.new(file: local_options['user.analytics.path'], persistent_defaults: true) do
|
|
60
|
-
setting :disabled do
|
|
61
|
-
validate PDK::Config::Validator.boolean
|
|
62
|
-
default_to { PDK::Config.bolt_analytics_config.fetch('disabled', true) }
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
setting 'user-id' do
|
|
66
|
-
validate PDK::Config::Validator.uuid
|
|
67
|
-
default_to do
|
|
68
|
-
require 'securerandom'
|
|
69
|
-
|
|
70
|
-
PDK::Config.bolt_analytics_config.fetch('user-id', SecureRandom.uuid)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Display the feature flags
|
|
76
|
-
mount :pdk_feature_flags, PDK::Config::Namespace.new('pdk_feature_flags') do
|
|
77
|
-
setting 'available' do
|
|
78
|
-
default_to { PDK.available_feature_flags }
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
setting 'requested' do
|
|
82
|
-
default_to { PDK.requested_feature_flags }
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# The project level configuration settings.
|
|
89
|
-
# @return [PDK::Config::Namespace]
|
|
90
|
-
# @api private
|
|
91
|
-
def project_config
|
|
92
|
-
context = @config_options['context']
|
|
93
|
-
@project ||= PDK::Config::Namespace.new('project') do
|
|
94
|
-
if context.is_a?(PDK::Context::ControlRepo)
|
|
95
|
-
mount :environment, PDK::ControlRepo.environment_conf_as_config(File.join(context.root_path, 'environment.conf'))
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
mount :validate, PDK::Config::YAML.new('validate', file: File.join(context.root_path, 'pdk.yaml'), persistent_defaults: true) do
|
|
99
|
-
setting 'ignore' do
|
|
100
|
-
default_to { [] }
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Resolves *all* filtered settings from all namespaces
|
|
107
|
-
#
|
|
108
|
-
# @param filter [String] Only resolve setting names which match the filter. See PDK::Config::Namespace.be_resolved? for matching rules
|
|
109
|
-
# @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
|
|
110
|
-
def resolve(filter = nil)
|
|
111
|
-
all_scopes.values.reverse.reduce({}) do |result, method_name|
|
|
112
|
-
result.merge(send(method_name).resolve(filter))
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Returns a configuration setting by name. This name can either be a String, Array or parameters e.g. These are equivalent
|
|
117
|
-
# - PDK.config.get('user.a.b.c')
|
|
118
|
-
# - PDK.config.get(['user', 'a', 'b', 'c'])
|
|
119
|
-
# - PDK.config.get('user', 'a', 'b', 'c')
|
|
120
|
-
# @param root [Array[String], String] The root setting name or the entire setting name as a single string
|
|
121
|
-
# @param keys [String] The child names of the setting
|
|
122
|
-
# @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
|
|
123
|
-
def get(root, *keys)
|
|
124
|
-
return nil if root.nil? || root.empty?
|
|
125
|
-
|
|
126
|
-
if keys.empty?
|
|
127
|
-
if root.is_a?(Array)
|
|
128
|
-
name = root
|
|
129
|
-
elsif root.is_a?(String)
|
|
130
|
-
name = split_key_string(root)
|
|
131
|
-
else
|
|
132
|
-
return nil
|
|
133
|
-
end
|
|
134
|
-
else
|
|
135
|
-
name = [root].concat(keys)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
get_within_scopes(name[1..-1], [name[0]])
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# 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
|
|
142
|
-
# @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
|
|
143
|
-
# @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
|
|
144
|
-
# @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
|
|
145
|
-
def get_within_scopes(setting_name, scopes = nil)
|
|
146
|
-
raise ArgumentError, _('Expected an Array but got \'%{klass}\' for scopes') % { klass: scopes.class } unless scopes.nil? || scopes.is_a?(Array)
|
|
147
|
-
raise ArgumentError, _('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)
|
|
148
|
-
|
|
149
|
-
setting_arr = setting_name.is_a?(String) ? split_key_string(setting_name) : setting_name
|
|
150
|
-
all_scope_names = all_scopes.keys
|
|
151
|
-
|
|
152
|
-
# Use only valid scope names
|
|
153
|
-
scopes = scopes.nil? ? all_scope_names : scopes & all_scope_names
|
|
154
|
-
|
|
155
|
-
scopes.each do |scope_name|
|
|
156
|
-
value = traverse_object(send(all_scopes[scope_name]), *setting_arr)
|
|
157
|
-
return value unless value.nil?
|
|
158
|
-
end
|
|
159
|
-
nil
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# 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
|
|
163
|
-
# @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
|
|
164
|
-
# @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
|
|
165
|
-
# @yield [PDK::Config::Namespace, Object] The value of the configuration setting. Does not yield if the setting does not exist or is nil
|
|
166
|
-
def with_scoped_value(setting_name, scopes = nil)
|
|
167
|
-
raise ArgumentError, _('must be passed a block') unless block_given?
|
|
168
|
-
value = get_within_scopes(setting_name, scopes)
|
|
169
|
-
yield value unless value.nil?
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Sets a configuration setting by name. This name can either be a String or an Array
|
|
173
|
-
# - PDK.config.set('user.a.b.c', ...)
|
|
174
|
-
# - PDK.config.set(['user', 'a', 'b', 'c'], ...)
|
|
175
|
-
# @param key [String, Array[String]] The name of the configuration key to change
|
|
176
|
-
# @param value [Object] The value to set the configuration setting to
|
|
177
|
-
# @param options [Hash] Changes the behaviour of the setting process
|
|
178
|
-
# @option options [Boolean] :force Disables any munging or array processing, and sets the value as it is. Default is false
|
|
179
|
-
# @return [Object] The new value of the configuration setting
|
|
180
|
-
def set(key, value, options = {})
|
|
181
|
-
options = {
|
|
182
|
-
force: false,
|
|
183
|
-
}.merge(options)
|
|
184
|
-
|
|
185
|
-
names = key.is_a?(String) ? split_key_string(key) : key
|
|
186
|
-
raise ArgumentError, _('Invalid configuration names') if names.nil? || !names.is_a?(Array) || names.empty?
|
|
187
|
-
scope_name = names[0]
|
|
188
|
-
raise ArgumentError, _("Unknown configuration root '%{name}'") % { name: scope_name } if all_scopes[scope_name].nil?
|
|
189
|
-
deep_set_object(value, options[:force], send(all_scopes[scope_name]), *names[1..-1])
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
def self.bolt_analytics_config
|
|
193
|
-
file = PDK::Util::Filesystem.expand_path('~/.puppetlabs/bolt/analytics.yaml')
|
|
194
|
-
PDK::Config::YAML.new(file: file)
|
|
195
|
-
rescue PDK::Config::LoadError => e
|
|
196
|
-
PDK.logger.debug _('Unable to load %{file}: %{message}') % {
|
|
197
|
-
file: file,
|
|
198
|
-
message: e.message,
|
|
199
|
-
}
|
|
200
|
-
PDK::Config::YAML.new
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def self.analytics_config_path
|
|
204
|
-
PDK::Util::Env['PDK_ANALYTICS_CONFIG'] || File.join(File.dirname(PDK::Util.configdir), 'puppet', 'analytics.yml')
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
def self.user_config_path
|
|
208
|
-
File.join(PDK::Util.configdir, 'user_config.json')
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def self.system_config_path
|
|
212
|
-
File.join(PDK::Util.system_configdir, 'system_config.json')
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def self.system_answers_path
|
|
216
|
-
File.join(PDK::Util.system_configdir, 'answers.json')
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
def self.json_schemas_path
|
|
220
|
-
File.join(__dir__, 'config')
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# return nil if not exist
|
|
224
|
-
def self.json_schema(name)
|
|
225
|
-
File.join(json_schemas_path, name + '_schema.json')
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
def self.analytics_config_exist?
|
|
229
|
-
PDK::Util::Filesystem.file?(analytics_config_path)
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
def self.analytics_config_interview!
|
|
233
|
-
require 'pdk/cli/util'
|
|
234
|
-
|
|
235
|
-
return unless PDK::CLI::Util.interactive?
|
|
236
|
-
|
|
237
|
-
pre_message = _(
|
|
238
|
-
'PDK collects anonymous usage information to help us understand how ' \
|
|
239
|
-
'it is being used and make decisions on how to improve it. You can ' \
|
|
240
|
-
'find out more about what data we collect and how it is used in the ' \
|
|
241
|
-
"PDK documentation at %{url}.\n",
|
|
242
|
-
) % { url: 'https://puppet.com/docs/pdk/latest/pdk_install.html' }
|
|
243
|
-
post_message = _(
|
|
244
|
-
'You can opt in or out of the usage data collection at any time by ' \
|
|
245
|
-
'editing the analytics configuration file at %{path} and changing ' \
|
|
246
|
-
"the '%{key}' value.",
|
|
247
|
-
) % {
|
|
248
|
-
path: PDK::Config.analytics_config_path,
|
|
249
|
-
key: 'disabled',
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
questions = [
|
|
253
|
-
{
|
|
254
|
-
name: 'enabled',
|
|
255
|
-
question: _('Do you consent to the collection of anonymous PDK usage information?'),
|
|
256
|
-
type: :yes,
|
|
257
|
-
},
|
|
258
|
-
]
|
|
259
|
-
|
|
260
|
-
require 'pdk/cli/util/interview'
|
|
261
|
-
|
|
262
|
-
PDK.logger.info(text: pre_message, wrap: true)
|
|
263
|
-
prompt = TTY::Prompt.new(help_color: :cyan)
|
|
264
|
-
interview = PDK::CLI::Util::Interview.new(prompt)
|
|
265
|
-
interview.add_questions(questions)
|
|
266
|
-
answers = interview.run
|
|
267
|
-
|
|
268
|
-
if answers.nil?
|
|
269
|
-
PDK.logger.info _('No answer given, opting out of analytics collection.')
|
|
270
|
-
PDK.config.set(%w[user analytics disabled], true)
|
|
271
|
-
else
|
|
272
|
-
PDK.config.set(%w[user analytics disabled], !answers['enabled'])
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
PDK.logger.info(text: post_message, wrap: true)
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
private
|
|
279
|
-
|
|
280
|
-
#:nocov: This is a private method and is tested elsewhere
|
|
281
|
-
def traverse_object(object, *names)
|
|
282
|
-
return nil if object.nil? || !object.respond_to?(:[])
|
|
283
|
-
return nil if names.nil?
|
|
284
|
-
# It's possible to pass in empty names at the root traversal layer
|
|
285
|
-
# but this should _only_ happen at the root namespace level
|
|
286
|
-
if names.empty?
|
|
287
|
-
return (object.is_a?(PDK::Config::Namespace) ? object : nil)
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
name = names.shift
|
|
291
|
-
value = object[name]
|
|
292
|
-
if names.empty?
|
|
293
|
-
return value if value.is_a?(PDK::Config::Namespace)
|
|
294
|
-
# Duplicate arrays and hashes so that they are isolated from changes being made
|
|
295
|
-
(value.is_a?(Hash) || value.is_a?(Array)) ? value.dup : value
|
|
296
|
-
else
|
|
297
|
-
traverse_object(value, *names)
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
#:nocov:
|
|
301
|
-
|
|
302
|
-
#:nocov: This is a private method and is tested elsewhere
|
|
303
|
-
# Takes a string representation of a setting and splits into its constituent setting parts e.g.
|
|
304
|
-
# 'user.a.b.c' becomes ['user', 'a', 'b', 'c']
|
|
305
|
-
# @return [Array[String]] The string split into each setting name as an array
|
|
306
|
-
def split_key_string(key)
|
|
307
|
-
raise ArgumentError, _('Expected a String but got \'%{klass}\'') % { klass: key.class } unless key.is_a?(String)
|
|
308
|
-
key.split('.')
|
|
309
|
-
end
|
|
310
|
-
#:nocov:
|
|
311
|
-
|
|
312
|
-
#:nocov:
|
|
313
|
-
# Returns all known scope names and their associated method name to call, to query the scope
|
|
314
|
-
# Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
|
|
315
|
-
# @return [Hash[String, Symbol]] A hash of the scope name then method name to call to query the scope (as a Symbol)
|
|
316
|
-
def all_scopes
|
|
317
|
-
# Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
|
|
318
|
-
{
|
|
319
|
-
'project' => :project_config,
|
|
320
|
-
'user' => :user_config,
|
|
321
|
-
'system' => :system_config,
|
|
322
|
-
}.freeze
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
#:nocov: This is a private method and is tested elsewhere
|
|
326
|
-
# Deeply traverses an object tree via `[]` and sets the last
|
|
327
|
-
# element to the value specified.
|
|
328
|
-
#
|
|
329
|
-
# Creating any missing parent hashes during the traversal
|
|
330
|
-
def deep_set_object(value, force, namespace, *names)
|
|
331
|
-
raise ArgumentError, _('Missing or invalid namespace') unless namespace.is_a?(PDK::Config::Namespace)
|
|
332
|
-
raise ArgumentError, _('Missing a name to set') if names.nil? || names.empty?
|
|
333
|
-
|
|
334
|
-
name = names.shift
|
|
335
|
-
current_value = namespace[name]
|
|
336
|
-
|
|
337
|
-
# If the next thing in the traversal chain is another namespace, set the value using that child namespace.
|
|
338
|
-
if current_value.is_a?(PDK::Config::Namespace)
|
|
339
|
-
return deep_set_object(value, force, current_value, *names)
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
# We're at the end of the name traversal
|
|
343
|
-
if names.empty?
|
|
344
|
-
if force || !current_value.is_a?(Array)
|
|
345
|
-
namespace[name] = value
|
|
346
|
-
return value
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
# Arrays are a special case if we're not forcing the value
|
|
350
|
-
namespace[name] = current_value << value unless current_value.include?(value)
|
|
351
|
-
return value
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
# Need to generate a deep hash using the current remaining names
|
|
355
|
-
# So given an origin *names of ['a', 'b', 'c', 'd'] and a value 'foo',
|
|
356
|
-
# we eventually want a hash of `{"b"=>{"c"=>{"d"=>"foo"}}}`
|
|
357
|
-
#
|
|
358
|
-
# The code above has already shifted the first element so we currently have
|
|
359
|
-
# name : 'a'
|
|
360
|
-
# names: ['b', 'c', 'd']
|
|
361
|
-
#
|
|
362
|
-
#
|
|
363
|
-
# First we need to pop off the last element ('d') in this case as we need to set that in the `reduce` call below
|
|
364
|
-
# So now we have:
|
|
365
|
-
# name : 'a'
|
|
366
|
-
# names: ['b', 'c']
|
|
367
|
-
# last_name : 'd'
|
|
368
|
-
last_name = names.pop
|
|
369
|
-
# Using reduce and an accumulator, we create the nested hash from the deepest value first. In this case the deepest value
|
|
370
|
-
# is the last_name, so the starting condition is {"d"=>"foo"}
|
|
371
|
-
# After the first iteration ('c'), the accumulator has {"c"=>{"d"=>"foo"}}}
|
|
372
|
-
# After the last iteration ('b'), the accumulator has {"b"=>{"c"=>{"d"=>"foo"}}}
|
|
373
|
-
hash_value = names.reverse.reduce(last_name => value) { |accumulator, item| { item => accumulator } }
|
|
374
|
-
|
|
375
|
-
# If the current value is nil, then it can't be a namespace or an existing value
|
|
376
|
-
# or
|
|
377
|
-
# If the current value is not a Hash and are forcing the change.
|
|
378
|
-
if current_value.nil? || (force && !current_value.is_a?(Hash))
|
|
379
|
-
namespace[name] = hash_value
|
|
380
|
-
return value
|
|
381
|
-
end
|
|
382
|
-
|
|
383
|
-
raise ArgumentError, _("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)
|
|
384
|
-
|
|
385
|
-
namespace[name] = current_value.merge(hash_value)
|
|
386
|
-
value
|
|
387
|
-
end
|
|
388
|
-
#:nocov:
|
|
389
|
-
end
|
|
390
|
-
end
|
|
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 [String] 'user.analytics.path' Path to the user analytics PDK configuration file
|
|
23
|
+
# @option options [PDK::Context::AbstractContext] 'context' The context that the configuration should be created in
|
|
24
|
+
def initialize(options = nil)
|
|
25
|
+
options = {} if options.nil?
|
|
26
|
+
@config_options = {
|
|
27
|
+
'system.path' => PDK::Config.system_config_path,
|
|
28
|
+
'system.module_defaults.path' => PDK::Config.system_answers_path,
|
|
29
|
+
'user.path' => PDK::Config.user_config_path,
|
|
30
|
+
'user.module_defaults.path' => PDK::AnswerFile.default_answer_file_path,
|
|
31
|
+
'user.analytics.path' => PDK::Config.analytics_config_path,
|
|
32
|
+
'context' => PDK.context,
|
|
33
|
+
}.merge(options)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The system level configuration settings.
|
|
37
|
+
# @return [PDK::Config::Namespace]
|
|
38
|
+
# @api private
|
|
39
|
+
def system_config
|
|
40
|
+
local_options = @config_options
|
|
41
|
+
@system_config ||= PDK::Config::JSON.new('system', file: local_options['system.path']) do
|
|
42
|
+
mount :module_defaults, PDK::Config::JSON.new(file: local_options['system.module_defaults.path'])
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The user level configuration settings.
|
|
47
|
+
# @return [PDK::Config::Namespace]
|
|
48
|
+
# @api private
|
|
49
|
+
def user_config
|
|
50
|
+
local_options = @config_options
|
|
51
|
+
@user_config ||= PDK::Config::JSON.new('user', file: local_options['user.path']) do
|
|
52
|
+
mount :module_defaults, PDK::Config::JSON.new(file: local_options['user.module_defaults.path'])
|
|
53
|
+
|
|
54
|
+
# Due to the json-schema gem having issues with Windows based paths, and only supporting Draft 05 (or less) do
|
|
55
|
+
# not use JSON validation yet. Once PDK drops support for EOL rubies, we will be able to use the json_schemer gem
|
|
56
|
+
# Which has much more modern support
|
|
57
|
+
# Reference - https://github.com/puppetlabs/pdk/pull/777
|
|
58
|
+
# Reference - https://tickets.puppetlabs.com/browse/PDK-1526
|
|
59
|
+
mount :analytics, PDK::Config::YAML.new(file: local_options['user.analytics.path'], persistent_defaults: true) do
|
|
60
|
+
setting :disabled do
|
|
61
|
+
validate PDK::Config::Validator.boolean
|
|
62
|
+
default_to { PDK::Config.bolt_analytics_config.fetch('disabled', true) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
setting 'user-id' do
|
|
66
|
+
validate PDK::Config::Validator.uuid
|
|
67
|
+
default_to do
|
|
68
|
+
require 'securerandom'
|
|
69
|
+
|
|
70
|
+
PDK::Config.bolt_analytics_config.fetch('user-id', SecureRandom.uuid)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Display the feature flags
|
|
76
|
+
mount :pdk_feature_flags, PDK::Config::Namespace.new('pdk_feature_flags') do
|
|
77
|
+
setting 'available' do
|
|
78
|
+
default_to { PDK.available_feature_flags }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
setting 'requested' do
|
|
82
|
+
default_to { PDK.requested_feature_flags }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The project level configuration settings.
|
|
89
|
+
# @return [PDK::Config::Namespace]
|
|
90
|
+
# @api private
|
|
91
|
+
def project_config
|
|
92
|
+
context = @config_options['context']
|
|
93
|
+
@project ||= PDK::Config::Namespace.new('project') do
|
|
94
|
+
if context.is_a?(PDK::Context::ControlRepo)
|
|
95
|
+
mount :environment, PDK::ControlRepo.environment_conf_as_config(File.join(context.root_path, 'environment.conf'))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
mount :validate, PDK::Config::YAML.new('validate', file: File.join(context.root_path, 'pdk.yaml'), persistent_defaults: true) do
|
|
99
|
+
setting 'ignore' do
|
|
100
|
+
default_to { [] }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Resolves *all* filtered settings from all namespaces
|
|
107
|
+
#
|
|
108
|
+
# @param filter [String] Only resolve setting names which match the filter. See PDK::Config::Namespace.be_resolved? for matching rules
|
|
109
|
+
# @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
|
|
110
|
+
def resolve(filter = nil)
|
|
111
|
+
all_scopes.values.reverse.reduce({}) do |result, method_name|
|
|
112
|
+
result.merge(send(method_name).resolve(filter))
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Returns a configuration setting by name. This name can either be a String, Array or parameters e.g. These are equivalent
|
|
117
|
+
# - PDK.config.get('user.a.b.c')
|
|
118
|
+
# - PDK.config.get(['user', 'a', 'b', 'c'])
|
|
119
|
+
# - PDK.config.get('user', 'a', 'b', 'c')
|
|
120
|
+
# @param root [Array[String], String] The root setting name or the entire setting name as a single string
|
|
121
|
+
# @param keys [String] The child names of the setting
|
|
122
|
+
# @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
|
|
123
|
+
def get(root, *keys)
|
|
124
|
+
return nil if root.nil? || root.empty?
|
|
125
|
+
|
|
126
|
+
if keys.empty?
|
|
127
|
+
if root.is_a?(Array)
|
|
128
|
+
name = root
|
|
129
|
+
elsif root.is_a?(String)
|
|
130
|
+
name = split_key_string(root)
|
|
131
|
+
else
|
|
132
|
+
return nil
|
|
133
|
+
end
|
|
134
|
+
else
|
|
135
|
+
name = [root].concat(keys)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
get_within_scopes(name[1..-1], [name[0]])
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# 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
|
|
142
|
+
# @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
|
|
143
|
+
# @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
|
|
144
|
+
# @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
|
|
145
|
+
def get_within_scopes(setting_name, scopes = nil)
|
|
146
|
+
raise ArgumentError, _('Expected an Array but got \'%{klass}\' for scopes') % { klass: scopes.class } unless scopes.nil? || scopes.is_a?(Array)
|
|
147
|
+
raise ArgumentError, _('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)
|
|
148
|
+
|
|
149
|
+
setting_arr = setting_name.is_a?(String) ? split_key_string(setting_name) : setting_name
|
|
150
|
+
all_scope_names = all_scopes.keys
|
|
151
|
+
|
|
152
|
+
# Use only valid scope names
|
|
153
|
+
scopes = scopes.nil? ? all_scope_names : scopes & all_scope_names
|
|
154
|
+
|
|
155
|
+
scopes.each do |scope_name|
|
|
156
|
+
value = traverse_object(send(all_scopes[scope_name]), *setting_arr)
|
|
157
|
+
return value unless value.nil?
|
|
158
|
+
end
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# 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
|
|
163
|
+
# @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
|
|
164
|
+
# @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
|
|
165
|
+
# @yield [PDK::Config::Namespace, Object] The value of the configuration setting. Does not yield if the setting does not exist or is nil
|
|
166
|
+
def with_scoped_value(setting_name, scopes = nil)
|
|
167
|
+
raise ArgumentError, _('must be passed a block') unless block_given?
|
|
168
|
+
value = get_within_scopes(setting_name, scopes)
|
|
169
|
+
yield value unless value.nil?
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Sets a configuration setting by name. This name can either be a String or an Array
|
|
173
|
+
# - PDK.config.set('user.a.b.c', ...)
|
|
174
|
+
# - PDK.config.set(['user', 'a', 'b', 'c'], ...)
|
|
175
|
+
# @param key [String, Array[String]] The name of the configuration key to change
|
|
176
|
+
# @param value [Object] The value to set the configuration setting to
|
|
177
|
+
# @param options [Hash] Changes the behaviour of the setting process
|
|
178
|
+
# @option options [Boolean] :force Disables any munging or array processing, and sets the value as it is. Default is false
|
|
179
|
+
# @return [Object] The new value of the configuration setting
|
|
180
|
+
def set(key, value, options = {})
|
|
181
|
+
options = {
|
|
182
|
+
force: false,
|
|
183
|
+
}.merge(options)
|
|
184
|
+
|
|
185
|
+
names = key.is_a?(String) ? split_key_string(key) : key
|
|
186
|
+
raise ArgumentError, _('Invalid configuration names') if names.nil? || !names.is_a?(Array) || names.empty?
|
|
187
|
+
scope_name = names[0]
|
|
188
|
+
raise ArgumentError, _("Unknown configuration root '%{name}'") % { name: scope_name } if all_scopes[scope_name].nil?
|
|
189
|
+
deep_set_object(value, options[:force], send(all_scopes[scope_name]), *names[1..-1])
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def self.bolt_analytics_config
|
|
193
|
+
file = PDK::Util::Filesystem.expand_path('~/.puppetlabs/bolt/analytics.yaml')
|
|
194
|
+
PDK::Config::YAML.new(file: file)
|
|
195
|
+
rescue PDK::Config::LoadError => e
|
|
196
|
+
PDK.logger.debug _('Unable to load %{file}: %{message}') % {
|
|
197
|
+
file: file,
|
|
198
|
+
message: e.message,
|
|
199
|
+
}
|
|
200
|
+
PDK::Config::YAML.new
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def self.analytics_config_path
|
|
204
|
+
PDK::Util::Env['PDK_ANALYTICS_CONFIG'] || File.join(File.dirname(PDK::Util.configdir), 'puppet', 'analytics.yml')
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def self.user_config_path
|
|
208
|
+
File.join(PDK::Util.configdir, 'user_config.json')
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def self.system_config_path
|
|
212
|
+
File.join(PDK::Util.system_configdir, 'system_config.json')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def self.system_answers_path
|
|
216
|
+
File.join(PDK::Util.system_configdir, 'answers.json')
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def self.json_schemas_path
|
|
220
|
+
File.join(__dir__, 'config')
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# return nil if not exist
|
|
224
|
+
def self.json_schema(name)
|
|
225
|
+
File.join(json_schemas_path, name + '_schema.json')
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def self.analytics_config_exist?
|
|
229
|
+
PDK::Util::Filesystem.file?(analytics_config_path)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def self.analytics_config_interview!
|
|
233
|
+
require 'pdk/cli/util'
|
|
234
|
+
|
|
235
|
+
return unless PDK::CLI::Util.interactive?
|
|
236
|
+
|
|
237
|
+
pre_message = _(
|
|
238
|
+
'PDK collects anonymous usage information to help us understand how ' \
|
|
239
|
+
'it is being used and make decisions on how to improve it. You can ' \
|
|
240
|
+
'find out more about what data we collect and how it is used in the ' \
|
|
241
|
+
"PDK documentation at %{url}.\n",
|
|
242
|
+
) % { url: 'https://puppet.com/docs/pdk/latest/pdk_install.html' }
|
|
243
|
+
post_message = _(
|
|
244
|
+
'You can opt in or out of the usage data collection at any time by ' \
|
|
245
|
+
'editing the analytics configuration file at %{path} and changing ' \
|
|
246
|
+
"the '%{key}' value.",
|
|
247
|
+
) % {
|
|
248
|
+
path: PDK::Config.analytics_config_path,
|
|
249
|
+
key: 'disabled',
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
questions = [
|
|
253
|
+
{
|
|
254
|
+
name: 'enabled',
|
|
255
|
+
question: _('Do you consent to the collection of anonymous PDK usage information?'),
|
|
256
|
+
type: :yes,
|
|
257
|
+
},
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
require 'pdk/cli/util/interview'
|
|
261
|
+
|
|
262
|
+
PDK.logger.info(text: pre_message, wrap: true)
|
|
263
|
+
prompt = TTY::Prompt.new(help_color: :cyan)
|
|
264
|
+
interview = PDK::CLI::Util::Interview.new(prompt)
|
|
265
|
+
interview.add_questions(questions)
|
|
266
|
+
answers = interview.run
|
|
267
|
+
|
|
268
|
+
if answers.nil?
|
|
269
|
+
PDK.logger.info _('No answer given, opting out of analytics collection.')
|
|
270
|
+
PDK.config.set(%w[user analytics disabled], true)
|
|
271
|
+
else
|
|
272
|
+
PDK.config.set(%w[user analytics disabled], !answers['enabled'])
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
PDK.logger.info(text: post_message, wrap: true)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
private
|
|
279
|
+
|
|
280
|
+
#:nocov: This is a private method and is tested elsewhere
|
|
281
|
+
def traverse_object(object, *names)
|
|
282
|
+
return nil if object.nil? || !object.respond_to?(:[])
|
|
283
|
+
return nil if names.nil?
|
|
284
|
+
# It's possible to pass in empty names at the root traversal layer
|
|
285
|
+
# but this should _only_ happen at the root namespace level
|
|
286
|
+
if names.empty?
|
|
287
|
+
return (object.is_a?(PDK::Config::Namespace) ? object : nil)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
name = names.shift
|
|
291
|
+
value = object[name]
|
|
292
|
+
if names.empty?
|
|
293
|
+
return value if value.is_a?(PDK::Config::Namespace)
|
|
294
|
+
# Duplicate arrays and hashes so that they are isolated from changes being made
|
|
295
|
+
(value.is_a?(Hash) || value.is_a?(Array)) ? value.dup : value
|
|
296
|
+
else
|
|
297
|
+
traverse_object(value, *names)
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
#:nocov:
|
|
301
|
+
|
|
302
|
+
#:nocov: This is a private method and is tested elsewhere
|
|
303
|
+
# Takes a string representation of a setting and splits into its constituent setting parts e.g.
|
|
304
|
+
# 'user.a.b.c' becomes ['user', 'a', 'b', 'c']
|
|
305
|
+
# @return [Array[String]] The string split into each setting name as an array
|
|
306
|
+
def split_key_string(key)
|
|
307
|
+
raise ArgumentError, _('Expected a String but got \'%{klass}\'') % { klass: key.class } unless key.is_a?(String)
|
|
308
|
+
key.split('.')
|
|
309
|
+
end
|
|
310
|
+
#:nocov:
|
|
311
|
+
|
|
312
|
+
#:nocov:
|
|
313
|
+
# Returns all known scope names and their associated method name to call, to query the scope
|
|
314
|
+
# Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
|
|
315
|
+
# @return [Hash[String, Symbol]] A hash of the scope name then method name to call to query the scope (as a Symbol)
|
|
316
|
+
def all_scopes
|
|
317
|
+
# Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
|
|
318
|
+
{
|
|
319
|
+
'project' => :project_config,
|
|
320
|
+
'user' => :user_config,
|
|
321
|
+
'system' => :system_config,
|
|
322
|
+
}.freeze
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
#:nocov: This is a private method and is tested elsewhere
|
|
326
|
+
# Deeply traverses an object tree via `[]` and sets the last
|
|
327
|
+
# element to the value specified.
|
|
328
|
+
#
|
|
329
|
+
# Creating any missing parent hashes during the traversal
|
|
330
|
+
def deep_set_object(value, force, namespace, *names)
|
|
331
|
+
raise ArgumentError, _('Missing or invalid namespace') unless namespace.is_a?(PDK::Config::Namespace)
|
|
332
|
+
raise ArgumentError, _('Missing a name to set') if names.nil? || names.empty?
|
|
333
|
+
|
|
334
|
+
name = names.shift
|
|
335
|
+
current_value = namespace[name]
|
|
336
|
+
|
|
337
|
+
# If the next thing in the traversal chain is another namespace, set the value using that child namespace.
|
|
338
|
+
if current_value.is_a?(PDK::Config::Namespace)
|
|
339
|
+
return deep_set_object(value, force, current_value, *names)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# We're at the end of the name traversal
|
|
343
|
+
if names.empty?
|
|
344
|
+
if force || !current_value.is_a?(Array)
|
|
345
|
+
namespace[name] = value
|
|
346
|
+
return value
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Arrays are a special case if we're not forcing the value
|
|
350
|
+
namespace[name] = current_value << value unless current_value.include?(value)
|
|
351
|
+
return value
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Need to generate a deep hash using the current remaining names
|
|
355
|
+
# So given an origin *names of ['a', 'b', 'c', 'd'] and a value 'foo',
|
|
356
|
+
# we eventually want a hash of `{"b"=>{"c"=>{"d"=>"foo"}}}`
|
|
357
|
+
#
|
|
358
|
+
# The code above has already shifted the first element so we currently have
|
|
359
|
+
# name : 'a'
|
|
360
|
+
# names: ['b', 'c', 'd']
|
|
361
|
+
#
|
|
362
|
+
#
|
|
363
|
+
# First we need to pop off the last element ('d') in this case as we need to set that in the `reduce` call below
|
|
364
|
+
# So now we have:
|
|
365
|
+
# name : 'a'
|
|
366
|
+
# names: ['b', 'c']
|
|
367
|
+
# last_name : 'd'
|
|
368
|
+
last_name = names.pop
|
|
369
|
+
# Using reduce and an accumulator, we create the nested hash from the deepest value first. In this case the deepest value
|
|
370
|
+
# is the last_name, so the starting condition is {"d"=>"foo"}
|
|
371
|
+
# After the first iteration ('c'), the accumulator has {"c"=>{"d"=>"foo"}}}
|
|
372
|
+
# After the last iteration ('b'), the accumulator has {"b"=>{"c"=>{"d"=>"foo"}}}
|
|
373
|
+
hash_value = names.reverse.reduce(last_name => value) { |accumulator, item| { item => accumulator } }
|
|
374
|
+
|
|
375
|
+
# If the current value is nil, then it can't be a namespace or an existing value
|
|
376
|
+
# or
|
|
377
|
+
# If the current value is not a Hash and are forcing the change.
|
|
378
|
+
if current_value.nil? || (force && !current_value.is_a?(Hash))
|
|
379
|
+
namespace[name] = hash_value
|
|
380
|
+
return value
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
raise ArgumentError, _("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)
|
|
384
|
+
|
|
385
|
+
namespace[name] = current_value.merge(hash_value)
|
|
386
|
+
value
|
|
387
|
+
end
|
|
388
|
+
#:nocov:
|
|
389
|
+
end
|
|
390
|
+
end
|