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/namespace.rb
CHANGED
|
@@ -1,354 +1,354 @@
|
|
|
1
|
-
require 'pdk'
|
|
2
|
-
|
|
3
|
-
module PDK
|
|
4
|
-
class Config
|
|
5
|
-
class Namespace
|
|
6
|
-
# @param value [String] the new name of this namespace.
|
|
7
|
-
attr_writer :name
|
|
8
|
-
|
|
9
|
-
# @return [String] the path to the file associated with the contents of
|
|
10
|
-
# this namespace.
|
|
11
|
-
attr_reader :file
|
|
12
|
-
|
|
13
|
-
# @return [self] the parent namespace of this namespace.
|
|
14
|
-
attr_accessor :parent
|
|
15
|
-
|
|
16
|
-
# Initialises the PDK::Config::Namespace object.
|
|
17
|
-
#
|
|
18
|
-
# @param name [String] the name of the namespace (defaults to nil).
|
|
19
|
-
# @param params [Hash{Symbol => Object}] keyword parameters for the
|
|
20
|
-
# method.
|
|
21
|
-
# @option params [String] :file the path to the file associated with the
|
|
22
|
-
# contents of the namespace (defaults to nil).
|
|
23
|
-
# @option params [self] :parent the parent {self} that this namespace is
|
|
24
|
-
# a child of (defaults to nil).
|
|
25
|
-
# @option params [self] :persistent_defaults whether default values should be persisted
|
|
26
|
-
# to disk when evaluated. By default they are not persisted to disk. This is typically
|
|
27
|
-
# used for settings which a randomly generated, instead of being deterministic, e.g. analytics user-id
|
|
28
|
-
# @param block [Proc] a block that is evaluated within the new instance.
|
|
29
|
-
def initialize(name = nil, file: nil, parent: nil, persistent_defaults: false, &block)
|
|
30
|
-
@file = PDK::Util::Filesystem.expand_path(file) unless file.nil?
|
|
31
|
-
@settings = {}
|
|
32
|
-
@name = name.to_s
|
|
33
|
-
@parent = parent
|
|
34
|
-
@persistent_defaults = persistent_defaults
|
|
35
|
-
@mounts = {}
|
|
36
|
-
@loaded_from_file = false
|
|
37
|
-
@read_only = false
|
|
38
|
-
|
|
39
|
-
instance_eval(&block) if block_given?
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Pre-configure a value in the namespace.
|
|
43
|
-
#
|
|
44
|
-
# Allows you to specify validators and a default value for value in the
|
|
45
|
-
# namespace (see PDK::Config::Value#initialize).
|
|
46
|
-
#
|
|
47
|
-
# @param key [String,Symbol] the name of the value.
|
|
48
|
-
# @param block [Proc] a block that is evaluated within the new [self].
|
|
49
|
-
#
|
|
50
|
-
# @return [nil]
|
|
51
|
-
def setting(key, &block)
|
|
52
|
-
@settings[key.to_s] ||= default_setting_class.new(key.to_s, self)
|
|
53
|
-
@settings[key.to_s].instance_eval(&block) if block_given?
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Mount a provided [self] (or subclass) into the namespace.
|
|
57
|
-
#
|
|
58
|
-
# @param key [String,Symbol] the name of the namespace to be mounted.
|
|
59
|
-
# @param obj [self] the namespace to be mounted.
|
|
60
|
-
# @param block [Proc] a block to be evaluated within the instance of the
|
|
61
|
-
# newly mounted namespace.
|
|
62
|
-
#
|
|
63
|
-
# @raise [ArgumentError] if the object to be mounted is not a {self} or
|
|
64
|
-
# subclass thereof.
|
|
65
|
-
#
|
|
66
|
-
# @return [self] the mounted namespace.
|
|
67
|
-
def mount(key, obj, &block)
|
|
68
|
-
raise ArgumentError, _('Only PDK::Config::Namespace objects can be mounted into a namespace') unless obj.is_a?(PDK::Config::Namespace)
|
|
69
|
-
obj.parent = self
|
|
70
|
-
obj.name = key.to_s
|
|
71
|
-
obj.instance_eval(&block) if block_given?
|
|
72
|
-
@mounts[key.to_s] = obj
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Create and mount a new child namespace.
|
|
76
|
-
#
|
|
77
|
-
# @param name [String,Symbol] the name of the new namespace.
|
|
78
|
-
# @param block [Proc]
|
|
79
|
-
def namespace(name, &block)
|
|
80
|
-
mount(name, PDK::Config::Namespace.new, &block)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Get the value of the named key.
|
|
84
|
-
#
|
|
85
|
-
# If there is a value for that key, return it. If not, follow the logic
|
|
86
|
-
# described in {#default_config_value} to determine the default value to
|
|
87
|
-
# return.
|
|
88
|
-
#
|
|
89
|
-
# @note Unlike a Ruby Hash, this will not return `nil` in the event that
|
|
90
|
-
# the key does not exist (see #fetch).
|
|
91
|
-
#
|
|
92
|
-
# @param key [String,Symbol] the name of the value to retrieve.
|
|
93
|
-
#
|
|
94
|
-
# @return [Object] the requested value.
|
|
95
|
-
def [](key)
|
|
96
|
-
# Check if it's a mount first...
|
|
97
|
-
return @mounts[key.to_s] unless @mounts[key.to_s].nil?
|
|
98
|
-
# Check if it's a setting, otherwise nil
|
|
99
|
-
return nil if settings[key.to_s].nil?
|
|
100
|
-
return settings[key.to_s].value unless settings[key.to_s].value.nil?
|
|
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)
|
|
103
|
-
return default_value if default_value.nil? || !@persistent_defaults
|
|
104
|
-
# Persist the default value
|
|
105
|
-
settings[key.to_s].value = default_value
|
|
106
|
-
save_data
|
|
107
|
-
default_value
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Get the value of the named key or the provided default value if not
|
|
111
|
-
# present. Note that this does not trigger persistent defaults
|
|
112
|
-
#
|
|
113
|
-
# This differs from {#[]} in an important way in that it allows you to
|
|
114
|
-
# return a default value, which is not possible using `[] || default` as
|
|
115
|
-
# non-existent values when accessed normally via {#[]} will be defaulted
|
|
116
|
-
# to a new Hash.
|
|
117
|
-
#
|
|
118
|
-
# @param key [String,Symbol] the name of the value to fetch.
|
|
119
|
-
# @param default_value [Object] the value to return if the namespace does
|
|
120
|
-
# not contain the requested value.
|
|
121
|
-
#
|
|
122
|
-
# @return [Object] the requested value.
|
|
123
|
-
def fetch(key, default_value)
|
|
124
|
-
# Check if it's a mount first...
|
|
125
|
-
return @mounts[key.to_s] unless @mounts[key.to_s].nil?
|
|
126
|
-
# Check if it's a setting, otherwise default_value
|
|
127
|
-
return default_value if settings[key.to_s].nil?
|
|
128
|
-
# Check if has a value, otherwise default_value
|
|
129
|
-
settings[key.to_s].value.nil? ? default_value : settings[key.to_s].value
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# After the value has been set in memory, the value will then be
|
|
133
|
-
# persisted to disk.
|
|
134
|
-
#
|
|
135
|
-
# @param key [String,Symbol] the name of the configuration value.
|
|
136
|
-
# @param value [Object] the value of the configuration value.
|
|
137
|
-
#
|
|
138
|
-
# @return [nil]
|
|
139
|
-
def []=(key, value)
|
|
140
|
-
# You can't set the value of a mount
|
|
141
|
-
raise ArgumentError, _('Namespace mounts can not be set a value') unless @mounts[key.to_s].nil?
|
|
142
|
-
set_volatile_value(key, value)
|
|
143
|
-
# Persist the change
|
|
144
|
-
save_data
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Convert the namespace into a Hash of values, suitable for serialising
|
|
148
|
-
# and persisting to disk.
|
|
149
|
-
#
|
|
150
|
-
# Child namespaces that are associated with their own files are excluded
|
|
151
|
-
# from the Hash (as their values will be persisted to their own files)
|
|
152
|
-
# and nil values are removed from the Hash.
|
|
153
|
-
#
|
|
154
|
-
# @return [Hash{String => Object}] the values from the namespace that
|
|
155
|
-
# should be persisted to disk.
|
|
156
|
-
def to_h
|
|
157
|
-
new_hash = {}
|
|
158
|
-
settings.each_pair { |k, v| new_hash[k] = v.value }
|
|
159
|
-
@mounts.each_pair { |k, mount_point| new_hash[k] = mount_point.to_h if mount_point.include_in_parent? }
|
|
160
|
-
new_hash.delete_if { |_, v| v.nil? }
|
|
161
|
-
new_hash
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# Resolves all filtered settings, including child namespaces, fully namespaced and filling in default values.
|
|
165
|
-
#
|
|
166
|
-
# @param filter [String] Only resolve setting names which match the filter. See #be_resolved? for matching rules
|
|
167
|
-
# @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
|
|
168
|
-
def resolve(filter = nil)
|
|
169
|
-
resolved = {}
|
|
170
|
-
# Resolve the settings
|
|
171
|
-
settings.values.each do |setting|
|
|
172
|
-
setting_name = setting.qualified_name
|
|
173
|
-
if be_resolved?(setting_name, filter)
|
|
174
|
-
resolved[setting_name] = setting.value.nil? ? setting.default : setting.value
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
# Resolve the mounts
|
|
178
|
-
@mounts.values.each { |mount| resolved.merge!(mount.resolve(filter)) }
|
|
179
|
-
resolved
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# @return [Boolean] true if the namespace has a parent, otherwise false.
|
|
183
|
-
def child_namespace?
|
|
184
|
-
!parent.nil?
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
# Determines the fully qualified name of the namespace.
|
|
188
|
-
#
|
|
189
|
-
# If this is a child namespace, then fully qualified name for the
|
|
190
|
-
# namespace will be "<parent>.<child>".
|
|
191
|
-
#
|
|
192
|
-
# @return [String] the fully qualifed name of the namespace.
|
|
193
|
-
def name
|
|
194
|
-
child_namespace? ? [parent.name, @name].join('.') : @name
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Determines if the contents of the namespace should be included in the
|
|
198
|
-
# parent namespace when persisting to disk.
|
|
199
|
-
#
|
|
200
|
-
# If the namespace has been mounted into a parent namespace and is not
|
|
201
|
-
# associated with its own file on disk, then the values in the namespace
|
|
202
|
-
# should be included in the parent namespace when persisting to disk.
|
|
203
|
-
#
|
|
204
|
-
# @return [Boolean] true if the values should be included in the parent
|
|
205
|
-
# namespace.
|
|
206
|
-
def include_in_parent?
|
|
207
|
-
child_namespace? && file.nil?
|
|
208
|
-
end
|
|
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
|
-
|
|
218
|
-
private
|
|
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
|
-
|
|
230
|
-
# Determines whether a setting name should be resolved using the filter
|
|
231
|
-
# Returns true when filter is nil.
|
|
232
|
-
# Returns true if the filter is exactly the same name as the setting.
|
|
233
|
-
# Returns true if the name is a sub-key of the filter e.g.
|
|
234
|
-
# Given a filter of user.module_defaults, `user.module_defaults.author` will return true, but `user.analytics.disabled` will return false.
|
|
235
|
-
#
|
|
236
|
-
# @param name [String] The setting name to test.
|
|
237
|
-
# @param filter [String] The filter used to test on the name.
|
|
238
|
-
# @return [Boolean] Whether the name passes the filter.
|
|
239
|
-
def be_resolved?(name, filter = nil)
|
|
240
|
-
return true if filter.nil? # If we're not filtering, this value should always be resolved
|
|
241
|
-
return true if name == filter # If it's exactly the same name then it should be resolved
|
|
242
|
-
name.start_with?(filter + '.') # If name is a subkey of the filter then it should be resolved
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# @abstract Subclass and override {#parse_file} to implement parsing logic
|
|
246
|
-
# for a particular config file format.
|
|
247
|
-
#
|
|
248
|
-
# @param data [String] The content of the file to be parsed.
|
|
249
|
-
# @param filename [String] The path to the file to be parsed.
|
|
250
|
-
#
|
|
251
|
-
# @yield [String, Object] the data to be loaded into the
|
|
252
|
-
# namespace.
|
|
253
|
-
def parse_file(_filename); end
|
|
254
|
-
|
|
255
|
-
# @abstract Subclass and override {#serialize_data} to implement generating
|
|
256
|
-
# logic for a particular config file format.
|
|
257
|
-
#
|
|
258
|
-
# @param data [Hash{String => Object}] the data stored in the namespace
|
|
259
|
-
#
|
|
260
|
-
# @return [String] the serialized contents of the namespace suitable for
|
|
261
|
-
# writing to disk.
|
|
262
|
-
def serialize_data(_data); end
|
|
263
|
-
|
|
264
|
-
# @abstract Subclass and override {#create_missing_setting} to implement logic
|
|
265
|
-
# when a setting is dynamically created, for example when attempting to
|
|
266
|
-
# set the value of an unknown setting
|
|
267
|
-
#
|
|
268
|
-
# @param data [Hash{String => Object}] the data stored in the namespace
|
|
269
|
-
#
|
|
270
|
-
# @return [String] the serialized contents of the namespace suitable for
|
|
271
|
-
# writing to disk.
|
|
272
|
-
def create_missing_setting(key, initial_value = nil)
|
|
273
|
-
# Need to use `@settings` and `@mounts` here to stop recursive calls
|
|
274
|
-
return unless @mounts[key.to_s].nil?
|
|
275
|
-
return unless @settings[key.to_s].nil?
|
|
276
|
-
@settings[key.to_s] = default_setting_class.new(key.to_s, self, initial_value)
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
# Set the value of the named key.
|
|
280
|
-
#
|
|
281
|
-
# If the key has been pre-configured with {#value}, then the value of the
|
|
282
|
-
# key will be validated against any validators that have been configured.
|
|
283
|
-
#
|
|
284
|
-
# @param key [String,Symbol] the name of the configuration value.
|
|
285
|
-
# @param value [Object] the value of the configuration value.
|
|
286
|
-
def set_volatile_value(key, value)
|
|
287
|
-
# Need to use `settings` here to force the backing file to be loaded
|
|
288
|
-
return create_missing_setting(key, value) if settings[key.to_s].nil?
|
|
289
|
-
# Need to use `@settings` here to stop recursive calls from []=
|
|
290
|
-
@settings[key.to_s].value = value
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
# Helper method to read files.
|
|
294
|
-
#
|
|
295
|
-
# @raise [PDK::Config::LoadError] if the file is removed during read.
|
|
296
|
-
# @raise [PDK::Config::LoadError] if the user doesn't have the
|
|
297
|
-
# permissions needed to read the file.
|
|
298
|
-
# @return [String,nil] the contents of the file or nil if the file does
|
|
299
|
-
# not exist.
|
|
300
|
-
def load_data(filename)
|
|
301
|
-
return if filename.nil?
|
|
302
|
-
return unless PDK::Util::Filesystem.file?(filename)
|
|
303
|
-
|
|
304
|
-
PDK::Util::Filesystem.read_file(filename)
|
|
305
|
-
rescue Errno::ENOENT => e
|
|
306
|
-
raise PDK::Config::LoadError, e.message
|
|
307
|
-
rescue Errno::EACCES
|
|
308
|
-
raise PDK::Config::LoadError, _('Unable to open %{file} for reading') % {
|
|
309
|
-
file: filename,
|
|
310
|
-
}
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
# Persist the contents of the namespace to disk.
|
|
314
|
-
#
|
|
315
|
-
# Directories will be automatically created and the contents of the
|
|
316
|
-
# namespace will be serialized automatically with {#serialize_data}.
|
|
317
|
-
#
|
|
318
|
-
# @raise [PDK::Config::LoadError] if one of the intermediary path components
|
|
319
|
-
# exist but is not a directory.
|
|
320
|
-
# @raise [PDK::Config::LoadError] if the user does not have the
|
|
321
|
-
# permissions needed to write the file.
|
|
322
|
-
#
|
|
323
|
-
# @return [nil]
|
|
324
|
-
def save_data
|
|
325
|
-
return if file.nil? || @read_only
|
|
326
|
-
|
|
327
|
-
PDK::Util::Filesystem.mkdir_p(File.dirname(file))
|
|
328
|
-
|
|
329
|
-
PDK::Util::Filesystem.write_file(file, serialize_data(to_h))
|
|
330
|
-
rescue Errno::EACCES
|
|
331
|
-
raise PDK::Config::LoadError, _('Unable to open %{file} for writing') % {
|
|
332
|
-
file: file,
|
|
333
|
-
}
|
|
334
|
-
rescue SystemCallError => e
|
|
335
|
-
raise PDK::Config::LoadError, e.message
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# Memoised accessor for the loaded data.
|
|
339
|
-
#
|
|
340
|
-
# @return [Hash<String => PDK::Config::Setting>] the contents of the namespace.
|
|
341
|
-
def settings
|
|
342
|
-
return @settings if @loaded_from_file
|
|
343
|
-
@loaded_from_file = true
|
|
344
|
-
return @settings if file.nil?
|
|
345
|
-
parse_file(file) do |key, parsed_setting|
|
|
346
|
-
# Create a settings chain if a setting already exists
|
|
347
|
-
parsed_setting.previous_setting = @settings[key] unless @settings[key].nil?
|
|
348
|
-
@settings[key] = parsed_setting
|
|
349
|
-
end
|
|
350
|
-
@settings
|
|
351
|
-
end
|
|
352
|
-
end
|
|
353
|
-
end
|
|
354
|
-
end
|
|
1
|
+
require 'pdk'
|
|
2
|
+
|
|
3
|
+
module PDK
|
|
4
|
+
class Config
|
|
5
|
+
class Namespace
|
|
6
|
+
# @param value [String] the new name of this namespace.
|
|
7
|
+
attr_writer :name
|
|
8
|
+
|
|
9
|
+
# @return [String] the path to the file associated with the contents of
|
|
10
|
+
# this namespace.
|
|
11
|
+
attr_reader :file
|
|
12
|
+
|
|
13
|
+
# @return [self] the parent namespace of this namespace.
|
|
14
|
+
attr_accessor :parent
|
|
15
|
+
|
|
16
|
+
# Initialises the PDK::Config::Namespace object.
|
|
17
|
+
#
|
|
18
|
+
# @param name [String] the name of the namespace (defaults to nil).
|
|
19
|
+
# @param params [Hash{Symbol => Object}] keyword parameters for the
|
|
20
|
+
# method.
|
|
21
|
+
# @option params [String] :file the path to the file associated with the
|
|
22
|
+
# contents of the namespace (defaults to nil).
|
|
23
|
+
# @option params [self] :parent the parent {self} that this namespace is
|
|
24
|
+
# a child of (defaults to nil).
|
|
25
|
+
# @option params [self] :persistent_defaults whether default values should be persisted
|
|
26
|
+
# to disk when evaluated. By default they are not persisted to disk. This is typically
|
|
27
|
+
# used for settings which a randomly generated, instead of being deterministic, e.g. analytics user-id
|
|
28
|
+
# @param block [Proc] a block that is evaluated within the new instance.
|
|
29
|
+
def initialize(name = nil, file: nil, parent: nil, persistent_defaults: false, &block)
|
|
30
|
+
@file = PDK::Util::Filesystem.expand_path(file) unless file.nil?
|
|
31
|
+
@settings = {}
|
|
32
|
+
@name = name.to_s
|
|
33
|
+
@parent = parent
|
|
34
|
+
@persistent_defaults = persistent_defaults
|
|
35
|
+
@mounts = {}
|
|
36
|
+
@loaded_from_file = false
|
|
37
|
+
@read_only = false
|
|
38
|
+
|
|
39
|
+
instance_eval(&block) if block_given?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Pre-configure a value in the namespace.
|
|
43
|
+
#
|
|
44
|
+
# Allows you to specify validators and a default value for value in the
|
|
45
|
+
# namespace (see PDK::Config::Value#initialize).
|
|
46
|
+
#
|
|
47
|
+
# @param key [String,Symbol] the name of the value.
|
|
48
|
+
# @param block [Proc] a block that is evaluated within the new [self].
|
|
49
|
+
#
|
|
50
|
+
# @return [nil]
|
|
51
|
+
def setting(key, &block)
|
|
52
|
+
@settings[key.to_s] ||= default_setting_class.new(key.to_s, self)
|
|
53
|
+
@settings[key.to_s].instance_eval(&block) if block_given?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Mount a provided [self] (or subclass) into the namespace.
|
|
57
|
+
#
|
|
58
|
+
# @param key [String,Symbol] the name of the namespace to be mounted.
|
|
59
|
+
# @param obj [self] the namespace to be mounted.
|
|
60
|
+
# @param block [Proc] a block to be evaluated within the instance of the
|
|
61
|
+
# newly mounted namespace.
|
|
62
|
+
#
|
|
63
|
+
# @raise [ArgumentError] if the object to be mounted is not a {self} or
|
|
64
|
+
# subclass thereof.
|
|
65
|
+
#
|
|
66
|
+
# @return [self] the mounted namespace.
|
|
67
|
+
def mount(key, obj, &block)
|
|
68
|
+
raise ArgumentError, _('Only PDK::Config::Namespace objects can be mounted into a namespace') unless obj.is_a?(PDK::Config::Namespace)
|
|
69
|
+
obj.parent = self
|
|
70
|
+
obj.name = key.to_s
|
|
71
|
+
obj.instance_eval(&block) if block_given?
|
|
72
|
+
@mounts[key.to_s] = obj
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Create and mount a new child namespace.
|
|
76
|
+
#
|
|
77
|
+
# @param name [String,Symbol] the name of the new namespace.
|
|
78
|
+
# @param block [Proc]
|
|
79
|
+
def namespace(name, &block)
|
|
80
|
+
mount(name, PDK::Config::Namespace.new, &block)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get the value of the named key.
|
|
84
|
+
#
|
|
85
|
+
# If there is a value for that key, return it. If not, follow the logic
|
|
86
|
+
# described in {#default_config_value} to determine the default value to
|
|
87
|
+
# return.
|
|
88
|
+
#
|
|
89
|
+
# @note Unlike a Ruby Hash, this will not return `nil` in the event that
|
|
90
|
+
# the key does not exist (see #fetch).
|
|
91
|
+
#
|
|
92
|
+
# @param key [String,Symbol] the name of the value to retrieve.
|
|
93
|
+
#
|
|
94
|
+
# @return [Object] the requested value.
|
|
95
|
+
def [](key)
|
|
96
|
+
# Check if it's a mount first...
|
|
97
|
+
return @mounts[key.to_s] unless @mounts[key.to_s].nil?
|
|
98
|
+
# Check if it's a setting, otherwise nil
|
|
99
|
+
return nil if settings[key.to_s].nil?
|
|
100
|
+
return settings[key.to_s].value unless settings[key.to_s].value.nil?
|
|
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)
|
|
103
|
+
return default_value if default_value.nil? || !@persistent_defaults
|
|
104
|
+
# Persist the default value
|
|
105
|
+
settings[key.to_s].value = default_value
|
|
106
|
+
save_data
|
|
107
|
+
default_value
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Get the value of the named key or the provided default value if not
|
|
111
|
+
# present. Note that this does not trigger persistent defaults
|
|
112
|
+
#
|
|
113
|
+
# This differs from {#[]} in an important way in that it allows you to
|
|
114
|
+
# return a default value, which is not possible using `[] || default` as
|
|
115
|
+
# non-existent values when accessed normally via {#[]} will be defaulted
|
|
116
|
+
# to a new Hash.
|
|
117
|
+
#
|
|
118
|
+
# @param key [String,Symbol] the name of the value to fetch.
|
|
119
|
+
# @param default_value [Object] the value to return if the namespace does
|
|
120
|
+
# not contain the requested value.
|
|
121
|
+
#
|
|
122
|
+
# @return [Object] the requested value.
|
|
123
|
+
def fetch(key, default_value)
|
|
124
|
+
# Check if it's a mount first...
|
|
125
|
+
return @mounts[key.to_s] unless @mounts[key.to_s].nil?
|
|
126
|
+
# Check if it's a setting, otherwise default_value
|
|
127
|
+
return default_value if settings[key.to_s].nil?
|
|
128
|
+
# Check if has a value, otherwise default_value
|
|
129
|
+
settings[key.to_s].value.nil? ? default_value : settings[key.to_s].value
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# After the value has been set in memory, the value will then be
|
|
133
|
+
# persisted to disk.
|
|
134
|
+
#
|
|
135
|
+
# @param key [String,Symbol] the name of the configuration value.
|
|
136
|
+
# @param value [Object] the value of the configuration value.
|
|
137
|
+
#
|
|
138
|
+
# @return [nil]
|
|
139
|
+
def []=(key, value)
|
|
140
|
+
# You can't set the value of a mount
|
|
141
|
+
raise ArgumentError, _('Namespace mounts can not be set a value') unless @mounts[key.to_s].nil?
|
|
142
|
+
set_volatile_value(key, value)
|
|
143
|
+
# Persist the change
|
|
144
|
+
save_data
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Convert the namespace into a Hash of values, suitable for serialising
|
|
148
|
+
# and persisting to disk.
|
|
149
|
+
#
|
|
150
|
+
# Child namespaces that are associated with their own files are excluded
|
|
151
|
+
# from the Hash (as their values will be persisted to their own files)
|
|
152
|
+
# and nil values are removed from the Hash.
|
|
153
|
+
#
|
|
154
|
+
# @return [Hash{String => Object}] the values from the namespace that
|
|
155
|
+
# should be persisted to disk.
|
|
156
|
+
def to_h
|
|
157
|
+
new_hash = {}
|
|
158
|
+
settings.each_pair { |k, v| new_hash[k] = v.value }
|
|
159
|
+
@mounts.each_pair { |k, mount_point| new_hash[k] = mount_point.to_h if mount_point.include_in_parent? }
|
|
160
|
+
new_hash.delete_if { |_, v| v.nil? }
|
|
161
|
+
new_hash
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Resolves all filtered settings, including child namespaces, fully namespaced and filling in default values.
|
|
165
|
+
#
|
|
166
|
+
# @param filter [String] Only resolve setting names which match the filter. See #be_resolved? for matching rules
|
|
167
|
+
# @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
|
|
168
|
+
def resolve(filter = nil)
|
|
169
|
+
resolved = {}
|
|
170
|
+
# Resolve the settings
|
|
171
|
+
settings.values.each do |setting|
|
|
172
|
+
setting_name = setting.qualified_name
|
|
173
|
+
if be_resolved?(setting_name, filter)
|
|
174
|
+
resolved[setting_name] = setting.value.nil? ? setting.default : setting.value
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
# Resolve the mounts
|
|
178
|
+
@mounts.values.each { |mount| resolved.merge!(mount.resolve(filter)) }
|
|
179
|
+
resolved
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @return [Boolean] true if the namespace has a parent, otherwise false.
|
|
183
|
+
def child_namespace?
|
|
184
|
+
!parent.nil?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Determines the fully qualified name of the namespace.
|
|
188
|
+
#
|
|
189
|
+
# If this is a child namespace, then fully qualified name for the
|
|
190
|
+
# namespace will be "<parent>.<child>".
|
|
191
|
+
#
|
|
192
|
+
# @return [String] the fully qualifed name of the namespace.
|
|
193
|
+
def name
|
|
194
|
+
child_namespace? ? [parent.name, @name].join('.') : @name
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Determines if the contents of the namespace should be included in the
|
|
198
|
+
# parent namespace when persisting to disk.
|
|
199
|
+
#
|
|
200
|
+
# If the namespace has been mounted into a parent namespace and is not
|
|
201
|
+
# associated with its own file on disk, then the values in the namespace
|
|
202
|
+
# should be included in the parent namespace when persisting to disk.
|
|
203
|
+
#
|
|
204
|
+
# @return [Boolean] true if the values should be included in the parent
|
|
205
|
+
# namespace.
|
|
206
|
+
def include_in_parent?
|
|
207
|
+
child_namespace? && file.nil?
|
|
208
|
+
end
|
|
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
|
+
|
|
218
|
+
private
|
|
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
|
+
|
|
230
|
+
# Determines whether a setting name should be resolved using the filter
|
|
231
|
+
# Returns true when filter is nil.
|
|
232
|
+
# Returns true if the filter is exactly the same name as the setting.
|
|
233
|
+
# Returns true if the name is a sub-key of the filter e.g.
|
|
234
|
+
# Given a filter of user.module_defaults, `user.module_defaults.author` will return true, but `user.analytics.disabled` will return false.
|
|
235
|
+
#
|
|
236
|
+
# @param name [String] The setting name to test.
|
|
237
|
+
# @param filter [String] The filter used to test on the name.
|
|
238
|
+
# @return [Boolean] Whether the name passes the filter.
|
|
239
|
+
def be_resolved?(name, filter = nil)
|
|
240
|
+
return true if filter.nil? # If we're not filtering, this value should always be resolved
|
|
241
|
+
return true if name == filter # If it's exactly the same name then it should be resolved
|
|
242
|
+
name.start_with?(filter + '.') # If name is a subkey of the filter then it should be resolved
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @abstract Subclass and override {#parse_file} to implement parsing logic
|
|
246
|
+
# for a particular config file format.
|
|
247
|
+
#
|
|
248
|
+
# @param data [String] The content of the file to be parsed.
|
|
249
|
+
# @param filename [String] The path to the file to be parsed.
|
|
250
|
+
#
|
|
251
|
+
# @yield [String, Object] the data to be loaded into the
|
|
252
|
+
# namespace.
|
|
253
|
+
def parse_file(_filename); end
|
|
254
|
+
|
|
255
|
+
# @abstract Subclass and override {#serialize_data} to implement generating
|
|
256
|
+
# logic for a particular config file format.
|
|
257
|
+
#
|
|
258
|
+
# @param data [Hash{String => Object}] the data stored in the namespace
|
|
259
|
+
#
|
|
260
|
+
# @return [String] the serialized contents of the namespace suitable for
|
|
261
|
+
# writing to disk.
|
|
262
|
+
def serialize_data(_data); end
|
|
263
|
+
|
|
264
|
+
# @abstract Subclass and override {#create_missing_setting} to implement logic
|
|
265
|
+
# when a setting is dynamically created, for example when attempting to
|
|
266
|
+
# set the value of an unknown setting
|
|
267
|
+
#
|
|
268
|
+
# @param data [Hash{String => Object}] the data stored in the namespace
|
|
269
|
+
#
|
|
270
|
+
# @return [String] the serialized contents of the namespace suitable for
|
|
271
|
+
# writing to disk.
|
|
272
|
+
def create_missing_setting(key, initial_value = nil)
|
|
273
|
+
# Need to use `@settings` and `@mounts` here to stop recursive calls
|
|
274
|
+
return unless @mounts[key.to_s].nil?
|
|
275
|
+
return unless @settings[key.to_s].nil?
|
|
276
|
+
@settings[key.to_s] = default_setting_class.new(key.to_s, self, initial_value)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Set the value of the named key.
|
|
280
|
+
#
|
|
281
|
+
# If the key has been pre-configured with {#value}, then the value of the
|
|
282
|
+
# key will be validated against any validators that have been configured.
|
|
283
|
+
#
|
|
284
|
+
# @param key [String,Symbol] the name of the configuration value.
|
|
285
|
+
# @param value [Object] the value of the configuration value.
|
|
286
|
+
def set_volatile_value(key, value)
|
|
287
|
+
# Need to use `settings` here to force the backing file to be loaded
|
|
288
|
+
return create_missing_setting(key, value) if settings[key.to_s].nil?
|
|
289
|
+
# Need to use `@settings` here to stop recursive calls from []=
|
|
290
|
+
@settings[key.to_s].value = value
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Helper method to read files.
|
|
294
|
+
#
|
|
295
|
+
# @raise [PDK::Config::LoadError] if the file is removed during read.
|
|
296
|
+
# @raise [PDK::Config::LoadError] if the user doesn't have the
|
|
297
|
+
# permissions needed to read the file.
|
|
298
|
+
# @return [String,nil] the contents of the file or nil if the file does
|
|
299
|
+
# not exist.
|
|
300
|
+
def load_data(filename)
|
|
301
|
+
return if filename.nil?
|
|
302
|
+
return unless PDK::Util::Filesystem.file?(filename)
|
|
303
|
+
|
|
304
|
+
PDK::Util::Filesystem.read_file(filename)
|
|
305
|
+
rescue Errno::ENOENT => e
|
|
306
|
+
raise PDK::Config::LoadError, e.message
|
|
307
|
+
rescue Errno::EACCES
|
|
308
|
+
raise PDK::Config::LoadError, _('Unable to open %{file} for reading') % {
|
|
309
|
+
file: filename,
|
|
310
|
+
}
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Persist the contents of the namespace to disk.
|
|
314
|
+
#
|
|
315
|
+
# Directories will be automatically created and the contents of the
|
|
316
|
+
# namespace will be serialized automatically with {#serialize_data}.
|
|
317
|
+
#
|
|
318
|
+
# @raise [PDK::Config::LoadError] if one of the intermediary path components
|
|
319
|
+
# exist but is not a directory.
|
|
320
|
+
# @raise [PDK::Config::LoadError] if the user does not have the
|
|
321
|
+
# permissions needed to write the file.
|
|
322
|
+
#
|
|
323
|
+
# @return [nil]
|
|
324
|
+
def save_data
|
|
325
|
+
return if file.nil? || @read_only
|
|
326
|
+
|
|
327
|
+
PDK::Util::Filesystem.mkdir_p(File.dirname(file))
|
|
328
|
+
|
|
329
|
+
PDK::Util::Filesystem.write_file(file, serialize_data(to_h))
|
|
330
|
+
rescue Errno::EACCES
|
|
331
|
+
raise PDK::Config::LoadError, _('Unable to open %{file} for writing') % {
|
|
332
|
+
file: file,
|
|
333
|
+
}
|
|
334
|
+
rescue SystemCallError => e
|
|
335
|
+
raise PDK::Config::LoadError, e.message
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Memoised accessor for the loaded data.
|
|
339
|
+
#
|
|
340
|
+
# @return [Hash<String => PDK::Config::Setting>] the contents of the namespace.
|
|
341
|
+
def settings
|
|
342
|
+
return @settings if @loaded_from_file
|
|
343
|
+
@loaded_from_file = true
|
|
344
|
+
return @settings if file.nil?
|
|
345
|
+
parse_file(file) do |key, parsed_setting|
|
|
346
|
+
# Create a settings chain if a setting already exists
|
|
347
|
+
parsed_setting.previous_setting = @settings[key] unless @settings[key].nil?
|
|
348
|
+
@settings[key] = parsed_setting
|
|
349
|
+
end
|
|
350
|
+
@settings
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|