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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +744 -711
- data/README.md +23 -21
- data/lib/pdk/answer_file.rb +3 -112
- data/lib/pdk/bolt.rb +20 -0
- data/lib/pdk/cli/build.rb +51 -54
- data/lib/pdk/cli/bundle.rb +33 -29
- data/lib/pdk/cli/console.rb +148 -0
- data/lib/pdk/cli/convert.rb +46 -37
- data/lib/pdk/cli/env.rb +51 -0
- data/lib/pdk/cli/errors.rb +4 -3
- data/lib/pdk/cli/exec/command.rb +285 -0
- data/lib/pdk/cli/exec/interactive_command.rb +109 -0
- data/lib/pdk/cli/exec.rb +32 -201
- data/lib/pdk/cli/exec_group.rb +79 -43
- data/lib/pdk/cli/get/config.rb +26 -0
- data/lib/pdk/cli/get.rb +22 -0
- data/lib/pdk/cli/new/class.rb +20 -22
- data/lib/pdk/cli/new/defined_type.rb +21 -21
- data/lib/pdk/cli/new/fact.rb +27 -0
- data/lib/pdk/cli/new/function.rb +27 -0
- data/lib/pdk/cli/new/module.rb +40 -29
- data/lib/pdk/cli/new/provider.rb +18 -18
- data/lib/pdk/cli/new/task.rb +23 -22
- data/lib/pdk/cli/new/test.rb +52 -0
- data/lib/pdk/cli/new/transport.rb +27 -0
- data/lib/pdk/cli/new.rb +15 -9
- data/lib/pdk/cli/release/prep.rb +39 -0
- data/lib/pdk/cli/release/publish.rb +46 -0
- data/lib/pdk/cli/release.rb +185 -0
- data/lib/pdk/cli/remove/config.rb +83 -0
- data/lib/pdk/cli/remove.rb +22 -0
- data/lib/pdk/cli/set/config.rb +121 -0
- data/lib/pdk/cli/set.rb +22 -0
- data/lib/pdk/cli/test/unit.rb +71 -69
- data/lib/pdk/cli/test.rb +9 -8
- data/lib/pdk/cli/update.rb +38 -21
- data/lib/pdk/cli/util/command_redirector.rb +13 -3
- data/lib/pdk/cli/util/interview.rb +25 -9
- data/lib/pdk/cli/util/option_normalizer.rb +6 -6
- data/lib/pdk/cli/util/option_validator.rb +19 -9
- data/lib/pdk/cli/util/spinner.rb +13 -0
- data/lib/pdk/cli/util/update_manager_printer.rb +82 -0
- data/lib/pdk/cli/util.rb +105 -48
- data/lib/pdk/cli/validate.rb +96 -111
- data/lib/pdk/cli.rb +134 -87
- data/lib/pdk/config/errors.rb +5 -0
- data/lib/pdk/config/ini_file.rb +184 -0
- data/lib/pdk/config/ini_file_setting.rb +35 -0
- data/lib/pdk/config/json.rb +35 -0
- data/lib/pdk/config/json_schema_namespace.rb +137 -0
- data/lib/pdk/config/json_schema_setting.rb +51 -0
- data/lib/pdk/config/json_with_schema.rb +47 -0
- data/lib/pdk/config/namespace.rb +362 -0
- data/lib/pdk/config/setting.rb +134 -0
- data/lib/pdk/config/task_schema.json +116 -0
- data/lib/pdk/config/validator.rb +31 -0
- data/lib/pdk/config/yaml.rb +41 -0
- data/lib/pdk/config/yaml_with_schema.rb +51 -0
- data/lib/pdk/config.rb +304 -0
- data/lib/pdk/context/control_repo.rb +61 -0
- data/lib/pdk/context/module.rb +28 -0
- data/lib/pdk/context/none.rb +22 -0
- data/lib/pdk/context.rb +98 -0
- data/lib/pdk/control_repo.rb +89 -0
- data/lib/pdk/generate/defined_type.rb +27 -33
- data/lib/pdk/generate/fact.rb +26 -0
- data/lib/pdk/generate/function.rb +49 -0
- data/lib/pdk/generate/module.rb +160 -153
- data/lib/pdk/generate/provider.rb +16 -69
- data/lib/pdk/generate/puppet_class.rb +27 -32
- data/lib/pdk/generate/puppet_object.rb +100 -159
- data/lib/pdk/generate/task.rb +34 -51
- data/lib/pdk/generate/transport.rb +34 -0
- data/lib/pdk/generate.rb +21 -8
- data/lib/pdk/logger.rb +24 -6
- data/lib/pdk/module/build.rb +125 -37
- data/lib/pdk/module/convert.rb +146 -65
- data/lib/pdk/module/metadata.rb +72 -71
- data/lib/pdk/module/release.rb +255 -0
- data/lib/pdk/module/update.rb +48 -37
- data/lib/pdk/module/update_manager.rb +75 -39
- data/lib/pdk/module.rb +10 -2
- data/lib/pdk/monkey_patches.rb +268 -0
- data/lib/pdk/report/event.rb +36 -48
- data/lib/pdk/report.rb +35 -22
- data/lib/pdk/template/fetcher/git.rb +84 -0
- data/lib/pdk/template/fetcher/local.rb +29 -0
- data/lib/pdk/template/fetcher.rb +100 -0
- data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +108 -0
- data/lib/pdk/template/renderer/v1/renderer.rb +131 -0
- data/lib/pdk/template/renderer/v1/template_file.rb +100 -0
- data/lib/pdk/template/renderer/v1.rb +25 -0
- data/lib/pdk/template/renderer.rb +97 -0
- data/lib/pdk/template/template_dir.rb +67 -0
- data/lib/pdk/template.rb +52 -0
- data/lib/pdk/tests/unit.rb +101 -51
- data/lib/pdk/util/bundler.rb +44 -42
- data/lib/pdk/util/changelog_generator.rb +138 -0
- data/lib/pdk/util/env.rb +48 -0
- data/lib/pdk/util/filesystem.rb +139 -2
- data/lib/pdk/util/git.rb +108 -8
- data/lib/pdk/util/json_finder.rb +86 -0
- data/lib/pdk/util/puppet_strings.rb +125 -0
- data/lib/pdk/util/puppet_version.rb +71 -87
- data/lib/pdk/util/ruby_version.rb +49 -25
- data/lib/pdk/util/template_uri.rb +283 -0
- data/lib/pdk/util/vendored_file.rb +34 -42
- data/lib/pdk/util/version.rb +11 -10
- data/lib/pdk/util/windows/api_types.rb +74 -44
- data/lib/pdk/util/windows/file.rb +31 -27
- data/lib/pdk/util/windows/process.rb +74 -0
- data/lib/pdk/util/windows/string.rb +19 -12
- data/lib/pdk/util/windows.rb +2 -0
- data/lib/pdk/util.rb +111 -124
- data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -0
- data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -0
- data/lib/pdk/validate/external_command_validator.rb +213 -0
- data/lib/pdk/validate/internal_ruby_validator.rb +101 -0
- data/lib/pdk/validate/invokable_validator.rb +238 -0
- data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +84 -0
- data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +76 -0
- data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
- data/lib/pdk/validate/puppet/puppet_epp_validator.rb +131 -0
- data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
- data/lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb +38 -0
- data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +135 -0
- data/lib/pdk/validate/puppet/puppet_validator_group.rb +22 -0
- data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +79 -0
- data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
- data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +83 -0
- data/lib/pdk/validate/tasks/tasks_name_validator.rb +45 -0
- data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
- data/lib/pdk/validate/validator.rb +120 -0
- data/lib/pdk/validate/validator_group.rb +107 -0
- data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +86 -0
- data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
- data/lib/pdk/validate.rb +86 -12
- data/lib/pdk/version.rb +2 -2
- data/lib/pdk.rb +60 -10
- metadata +138 -100
- data/lib/pdk/cli/module/build.rb +0 -14
- data/lib/pdk/cli/module/generate.rb +0 -45
- data/lib/pdk/cli/module.rb +0 -14
- data/lib/pdk/i18n.rb +0 -4
- data/lib/pdk/module/templatedir.rb +0 -321
- data/lib/pdk/template_file.rb +0 -95
- data/lib/pdk/validate/base_validator.rb +0 -215
- data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -86
- data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -109
- data/lib/pdk/validate/metadata_validator.rb +0 -30
- data/lib/pdk/validate/puppet/puppet_lint.rb +0 -67
- data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -112
- data/lib/pdk/validate/puppet_validator.rb +0 -30
- data/lib/pdk/validate/ruby/rubocop.rb +0 -77
- data/lib/pdk/validate/ruby_validator.rb +0 -29
- data/lib/pdk/validate/tasks/metadata_lint.rb +0 -126
- data/lib/pdk/validate/tasks/name.rb +0 -88
- data/lib/pdk/validate/tasks_validator.rb +0 -33
- data/lib/pdk/validate/yaml/syntax.rb +0 -109
- data/lib/pdk/validate/yaml_validator.rb +0 -31
- data/locales/config.yaml +0 -21
- data/locales/pdk.pot +0 -1291
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'pdk'
|
|
2
|
+
|
|
3
|
+
module PDK
|
|
4
|
+
class Config
|
|
5
|
+
class JSONSchemaSetting < PDK::Config::Setting
|
|
6
|
+
# Initialises the PDK::Config::JSONSchemaSetting object.
|
|
7
|
+
#
|
|
8
|
+
# @see PDK::Config::Setting.initialize
|
|
9
|
+
def initialize(_name, namespace, _initial_value)
|
|
10
|
+
raise 'The JSONSchemaSetting object can only be created within the JSONSchemaNamespace' unless namespace.is_a?(PDK::Config::JSONSchemaNamespace)
|
|
11
|
+
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Verifies that the new setting value is valid by calling the JSON schema validator on
|
|
16
|
+
# a hash which includes the new setting
|
|
17
|
+
#
|
|
18
|
+
# @see PDK::Config::Setting.validate!
|
|
19
|
+
def validate!(value)
|
|
20
|
+
# Get the existing namespace data
|
|
21
|
+
new_document = namespace.to_h
|
|
22
|
+
# ... set the new value
|
|
23
|
+
new_document[@name] = value
|
|
24
|
+
begin
|
|
25
|
+
# ... add validate it
|
|
26
|
+
namespace.validate_document!(new_document)
|
|
27
|
+
rescue ::JSON::Schema::ValidationError => e
|
|
28
|
+
raise ArgumentError, format('%{key} %{message}', key: qualified_name, message: e.message)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Evaluate the default setting, firstly from the JSON schema and then
|
|
33
|
+
# from any other default evaluators in the settings chain.
|
|
34
|
+
#
|
|
35
|
+
# @see PDK::Config::Setting.default
|
|
36
|
+
#
|
|
37
|
+
# @return [Object, nil] the result of evaluating the block given to
|
|
38
|
+
# {#default_to}, or `nil` if the setting has no default.
|
|
39
|
+
def default
|
|
40
|
+
# Return the default from the schema document if it exists
|
|
41
|
+
if namespace.schema_property_names.include?(@name)
|
|
42
|
+
prop_schema = namespace.schema['properties'][@name]
|
|
43
|
+
return prop_schema['default'] unless prop_schema['default'].nil?
|
|
44
|
+
end
|
|
45
|
+
# ... otherwise call the settings chain default
|
|
46
|
+
# and if that doesn't exist, just return nil
|
|
47
|
+
@previous_setting&.default
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'pdk'
|
|
2
|
+
|
|
3
|
+
module PDK
|
|
4
|
+
class Config
|
|
5
|
+
class JSONWithSchema < JSONSchemaNamespace
|
|
6
|
+
# Parses a JSON document with a schema.
|
|
7
|
+
#
|
|
8
|
+
# @see PDK::Config::Namespace.parse_file
|
|
9
|
+
def parse_file(filename)
|
|
10
|
+
raise unless block_given?
|
|
11
|
+
|
|
12
|
+
data = load_data(filename)
|
|
13
|
+
data = '{}' if data.nil? || data.empty?
|
|
14
|
+
require 'json'
|
|
15
|
+
|
|
16
|
+
@raw_data = ::JSON.parse(data)
|
|
17
|
+
@raw_data = {} if @raw_data.nil?
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
# Ensure the parsed document is actually valid
|
|
21
|
+
validate_document!(@raw_data)
|
|
22
|
+
rescue ::JSON::Schema::ValidationError => e
|
|
23
|
+
raise PDK::Config::LoadError, format('The configuration file %{filename} is not valid: %{message}', filename: filename, message: e.message)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
schema_property_names.each do |key|
|
|
27
|
+
yield key, PDK::Config::JSONSchemaSetting.new(key, self, @raw_data[key])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Remove all of the "known" settings from the schema and
|
|
31
|
+
# we're left with the settings that we don't manage.
|
|
32
|
+
self.unmanaged_settings = @raw_data.reject { |k, _| schema_property_names.include?(k) }
|
|
33
|
+
rescue ::JSON::ParserError => e
|
|
34
|
+
raise PDK::Config::LoadError, e.message
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Serializes object data into a JSON string.
|
|
38
|
+
#
|
|
39
|
+
# @see PDK::Config::Namespace.serialize_data
|
|
40
|
+
def serialize_data(data)
|
|
41
|
+
require 'json'
|
|
42
|
+
|
|
43
|
+
::JSON.pretty_generate(data)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,362 @@
|
|
|
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. module_defaults author
|
|
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
|
|
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
|
|
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
|
+
|
|
70
|
+
obj.parent = self
|
|
71
|
+
obj.name = key.to_s
|
|
72
|
+
obj.instance_eval(&block) if block
|
|
73
|
+
@mounts[key.to_s] = obj
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Create and mount a new child namespace.
|
|
77
|
+
#
|
|
78
|
+
# @param name [String,Symbol] the name of the new namespace.
|
|
79
|
+
# @param block [Proc]
|
|
80
|
+
def namespace(name, &block)
|
|
81
|
+
mount(name, PDK::Config::Namespace.new, &block)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Get the value of the named key.
|
|
85
|
+
#
|
|
86
|
+
# If there is a value for that key, return it. If not, follow the logic
|
|
87
|
+
# described in {#default_config_value} to determine the default value to
|
|
88
|
+
# return.
|
|
89
|
+
#
|
|
90
|
+
# @note Unlike a Ruby Hash, this will not return `nil` in the event that
|
|
91
|
+
# the key does not exist (see #fetch).
|
|
92
|
+
#
|
|
93
|
+
# @param key [String,Symbol] the name of the value to retrieve.
|
|
94
|
+
#
|
|
95
|
+
# @return [Object] the requested value.
|
|
96
|
+
def [](key)
|
|
97
|
+
# Check if it's a mount first...
|
|
98
|
+
return @mounts[key.to_s] unless @mounts[key.to_s].nil?
|
|
99
|
+
# Check if it's a setting, otherwise nil
|
|
100
|
+
return nil if settings[key.to_s].nil?
|
|
101
|
+
return settings[key.to_s].value unless settings[key.to_s].value.nil?
|
|
102
|
+
|
|
103
|
+
# Duplicate arrays and hashes so that they are isolated from changes being made
|
|
104
|
+
default_value = PDK::Util.deep_duplicate(settings[key.to_s].default)
|
|
105
|
+
return default_value if default_value.nil? || !@persistent_defaults
|
|
106
|
+
|
|
107
|
+
# Persist the default value
|
|
108
|
+
settings[key.to_s].value = default_value
|
|
109
|
+
save_data
|
|
110
|
+
default_value
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Get the value of the named key or the provided default value if not
|
|
114
|
+
# present. Note that this does not trigger persistent defaults
|
|
115
|
+
#
|
|
116
|
+
# This differs from {#[]} in an important way in that it allows you to
|
|
117
|
+
# return a default value, which is not possible using `[] || default` as
|
|
118
|
+
# non-existent values when accessed normally via {#[]} will be defaulted
|
|
119
|
+
# to a new Hash.
|
|
120
|
+
#
|
|
121
|
+
# @param key [String,Symbol] the name of the value to fetch.
|
|
122
|
+
# @param default_value [Object] the value to return if the namespace does
|
|
123
|
+
# not contain the requested value.
|
|
124
|
+
#
|
|
125
|
+
# @return [Object] the requested value.
|
|
126
|
+
def fetch(key, default_value)
|
|
127
|
+
# Check if it's a mount first...
|
|
128
|
+
return @mounts[key.to_s] unless @mounts[key.to_s].nil?
|
|
129
|
+
# Check if it's a setting, otherwise default_value
|
|
130
|
+
return default_value if settings[key.to_s].nil?
|
|
131
|
+
|
|
132
|
+
# Check if has a value, otherwise default_value
|
|
133
|
+
settings[key.to_s].value.nil? ? default_value : settings[key.to_s].value
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# After the value has been set in memory, the value will then be
|
|
137
|
+
# persisted to disk.
|
|
138
|
+
#
|
|
139
|
+
# @param key [String,Symbol] the name of the configuration value.
|
|
140
|
+
# @param value [Object] the value of the configuration value.
|
|
141
|
+
#
|
|
142
|
+
# @return [nil]
|
|
143
|
+
def []=(key, value)
|
|
144
|
+
# You can't set the value of a mount
|
|
145
|
+
raise ArgumentError, 'Namespace mounts can not be set a value' unless @mounts[key.to_s].nil?
|
|
146
|
+
|
|
147
|
+
set_volatile_value(key, value)
|
|
148
|
+
# Persist the change
|
|
149
|
+
save_data
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Convert the namespace into a Hash of values, suitable for serialising
|
|
153
|
+
# and persisting to disk.
|
|
154
|
+
#
|
|
155
|
+
# Child namespaces that are associated with their own files are excluded
|
|
156
|
+
# from the Hash (as their values will be persisted to their own files)
|
|
157
|
+
# and nil values are removed from the Hash.
|
|
158
|
+
#
|
|
159
|
+
# @return [Hash{String => Object}] the values from the namespace that
|
|
160
|
+
# should be persisted to disk.
|
|
161
|
+
def to_h
|
|
162
|
+
new_hash = {}
|
|
163
|
+
settings.each_pair { |k, v| new_hash[k] = v.value }
|
|
164
|
+
@mounts.each_pair { |k, mount_point| new_hash[k] = mount_point.to_h if mount_point.include_in_parent? }
|
|
165
|
+
new_hash.delete_if { |_k, v| v.nil? }
|
|
166
|
+
new_hash
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Resolves all filtered settings, including child namespaces, fully namespaced and filling in default values.
|
|
170
|
+
#
|
|
171
|
+
# @param filter [String] Only resolve setting names which match the filter. See #be_resolved? for matching rules
|
|
172
|
+
# @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
|
|
173
|
+
def resolve(filter = nil)
|
|
174
|
+
resolved = {}
|
|
175
|
+
# Resolve the settings
|
|
176
|
+
settings.each_value do |setting|
|
|
177
|
+
setting_name = setting.qualified_name
|
|
178
|
+
if be_resolved?(setting_name, filter)
|
|
179
|
+
resolved[setting_name] = setting.value.nil? ? setting.default : setting.value
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
# Resolve the mounts
|
|
183
|
+
@mounts.each_value { |mount| resolved.merge!(mount.resolve(filter)) }
|
|
184
|
+
resolved
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# @return [Boolean] true if the namespace has a parent, otherwise false.
|
|
188
|
+
def child_namespace?
|
|
189
|
+
!parent.nil?
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Determines the fully qualified name of the namespace.
|
|
193
|
+
#
|
|
194
|
+
# If this is a child namespace, then fully qualified name for the
|
|
195
|
+
# namespace will be "<parent>.<child>".
|
|
196
|
+
#
|
|
197
|
+
# @return [String] the fully qualifed name of the namespace.
|
|
198
|
+
def name
|
|
199
|
+
child_namespace? ? [parent.name, @name].join('.') : @name
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Determines if the contents of the namespace should be included in the
|
|
203
|
+
# parent namespace when persisting to disk.
|
|
204
|
+
#
|
|
205
|
+
# If the namespace has been mounted into a parent namespace and is not
|
|
206
|
+
# associated with its own file on disk, then the values in the namespace
|
|
207
|
+
# should be included in the parent namespace when persisting to disk.
|
|
208
|
+
#
|
|
209
|
+
# @return [Boolean] true if the values should be included in the parent
|
|
210
|
+
# namespace.
|
|
211
|
+
def include_in_parent?
|
|
212
|
+
child_namespace? && file.nil?
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Disables the namespace, and child namespaces, from writing changes to disk.
|
|
216
|
+
# Typically this is only needed for unit testing.
|
|
217
|
+
# @api private
|
|
218
|
+
def read_only!
|
|
219
|
+
@read_only = true
|
|
220
|
+
# pass the read_only! method as a block to the each_value method. This means that
|
|
221
|
+
# for each value in the @mounts hash, the read_only! method will be called on that value.
|
|
222
|
+
@mounts.each_value(&:read_only!)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
private
|
|
226
|
+
|
|
227
|
+
# Returns the object class to create settings with. Subclasses may override this to use specific setting classes
|
|
228
|
+
#
|
|
229
|
+
# @return [Class[PDK::Config::Setting]]
|
|
230
|
+
#
|
|
231
|
+
# @abstract
|
|
232
|
+
# @private
|
|
233
|
+
def default_setting_class
|
|
234
|
+
PDK::Config::Setting
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Determines whether a setting name should be resolved using the filter
|
|
238
|
+
# Returns true when filter is nil.
|
|
239
|
+
# Returns true if the filter is exactly the same name as the setting.
|
|
240
|
+
# Returns true if the name is a sub-key of the filter e.g.
|
|
241
|
+
# Given a filter of user.module_defaults, `user.module_defaults.author` will return true, but `user.pdk_feature_flags.requested` will return false.
|
|
242
|
+
#
|
|
243
|
+
# @param name [String] The setting name to test.
|
|
244
|
+
# @param filter [String] The filter used to test on the name.
|
|
245
|
+
# @return [Boolean] Whether the name passes the filter.
|
|
246
|
+
def be_resolved?(name, filter = nil)
|
|
247
|
+
return true if filter.nil? # If we're not filtering, this value should always be resolved
|
|
248
|
+
return true if name == filter # If it's exactly the same name then it should be resolved
|
|
249
|
+
|
|
250
|
+
name.start_with?("#{filter}.") # If name is a subkey of the filter then it should be resolved
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# @abstract Subclass and override {#parse_file} to implement parsing logic
|
|
254
|
+
# for a particular config file format.
|
|
255
|
+
#
|
|
256
|
+
# @param data [String] The content of the file to be parsed.
|
|
257
|
+
# @param filename [String] The path to the file to be parsed.
|
|
258
|
+
#
|
|
259
|
+
# @yield [String, Object] the data to be loaded into the
|
|
260
|
+
# namespace.
|
|
261
|
+
def parse_file(_filename); end
|
|
262
|
+
|
|
263
|
+
# @abstract Subclass and override {#serialize_data} to implement generating
|
|
264
|
+
# logic for a particular config file format.
|
|
265
|
+
#
|
|
266
|
+
# @param data [Hash{String => Object}] the data stored in the namespace
|
|
267
|
+
#
|
|
268
|
+
# @return [String] the serialized contents of the namespace suitable for
|
|
269
|
+
# writing to disk.
|
|
270
|
+
def serialize_data(_data); end
|
|
271
|
+
|
|
272
|
+
# @abstract Subclass and override {#create_missing_setting} to implement logic
|
|
273
|
+
# when a setting is dynamically created, for example when attempting to
|
|
274
|
+
# set the value of an unknown setting
|
|
275
|
+
#
|
|
276
|
+
# @param data [Hash{String => Object}] the data stored in the namespace
|
|
277
|
+
#
|
|
278
|
+
# @return [String] the serialized contents of the namespace suitable for
|
|
279
|
+
# writing to disk.
|
|
280
|
+
def create_missing_setting(key, initial_value = nil)
|
|
281
|
+
# Need to use `@settings` and `@mounts` here to stop recursive calls
|
|
282
|
+
return unless @mounts[key.to_s].nil?
|
|
283
|
+
return unless @settings[key.to_s].nil?
|
|
284
|
+
|
|
285
|
+
@settings[key.to_s] = default_setting_class.new(key.to_s, self, initial_value)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Set the value of the named key.
|
|
289
|
+
#
|
|
290
|
+
# If the key has been pre-configured with {#value}, then the value of the
|
|
291
|
+
# key will be validated against any validators that have been configured.
|
|
292
|
+
#
|
|
293
|
+
# @param key [String,Symbol] the name of the configuration value.
|
|
294
|
+
# @param value [Object] the value of the configuration value.
|
|
295
|
+
def set_volatile_value(key, value)
|
|
296
|
+
# Need to use `settings` here to force the backing file to be loaded
|
|
297
|
+
return create_missing_setting(key, value) if settings[key.to_s].nil?
|
|
298
|
+
|
|
299
|
+
# Need to use `@settings` here to stop recursive calls from []=
|
|
300
|
+
@settings[key.to_s].value = value
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Helper method to read files.
|
|
304
|
+
#
|
|
305
|
+
# @raise [PDK::Config::LoadError] if the file is removed during read.
|
|
306
|
+
# @raise [PDK::Config::LoadError] if the user doesn't have the
|
|
307
|
+
# permissions needed to read the file.
|
|
308
|
+
# @return [String,nil] the contents of the file or nil if the file does
|
|
309
|
+
# not exist.
|
|
310
|
+
def load_data(filename)
|
|
311
|
+
return if filename.nil?
|
|
312
|
+
return unless PDK::Util::Filesystem.file?(filename)
|
|
313
|
+
|
|
314
|
+
PDK::Util::Filesystem.read_file(filename)
|
|
315
|
+
rescue Errno::ENOENT => e
|
|
316
|
+
raise PDK::Config::LoadError, e.message
|
|
317
|
+
rescue Errno::EACCES
|
|
318
|
+
raise PDK::Config::LoadError, format('Unable to open %{file} for reading', file: filename)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Persist the contents of the namespace to disk.
|
|
322
|
+
#
|
|
323
|
+
# Directories will be automatically created and the contents of the
|
|
324
|
+
# namespace will be serialized automatically with {#serialize_data}.
|
|
325
|
+
#
|
|
326
|
+
# @raise [PDK::Config::LoadError] if one of the intermediary path components
|
|
327
|
+
# exist but is not a directory.
|
|
328
|
+
# @raise [PDK::Config::LoadError] if the user does not have the
|
|
329
|
+
# permissions needed to write the file.
|
|
330
|
+
#
|
|
331
|
+
# @return [nil]
|
|
332
|
+
def save_data
|
|
333
|
+
return if file.nil? || @read_only
|
|
334
|
+
|
|
335
|
+
PDK::Util::Filesystem.mkdir_p(File.dirname(file))
|
|
336
|
+
|
|
337
|
+
PDK::Util::Filesystem.write_file(file, serialize_data(to_h))
|
|
338
|
+
rescue Errno::EACCES
|
|
339
|
+
raise PDK::Config::LoadError, format('Unable to open %{file} for writing', file: file)
|
|
340
|
+
rescue SystemCallError => e
|
|
341
|
+
raise PDK::Config::LoadError, e.message
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Memoised accessor for the loaded data.
|
|
345
|
+
#
|
|
346
|
+
# @return [Hash<String => PDK::Config::Setting>] the contents of the namespace.
|
|
347
|
+
def settings
|
|
348
|
+
return @settings if @loaded_from_file
|
|
349
|
+
|
|
350
|
+
@loaded_from_file = true
|
|
351
|
+
return @settings if file.nil?
|
|
352
|
+
|
|
353
|
+
parse_file(file) do |key, parsed_setting|
|
|
354
|
+
# Create a settings chain if a setting already exists
|
|
355
|
+
parsed_setting.previous_setting = @settings[key] unless @settings[key].nil?
|
|
356
|
+
@settings[key] = parsed_setting
|
|
357
|
+
end
|
|
358
|
+
@settings
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
require 'pdk'
|
|
2
|
+
|
|
3
|
+
module PDK
|
|
4
|
+
class Config
|
|
5
|
+
# A class for describing the setting of a {PDK::Config} setting.
|
|
6
|
+
#
|
|
7
|
+
# Generally, this is never instantiated manually, but is instead
|
|
8
|
+
# instantiated by passing a block to {PDK::Config::Namespace#setting}.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
#
|
|
12
|
+
# PDK::Config::Namespace.new('module_defaults') do
|
|
13
|
+
# setting :author do
|
|
14
|
+
# validate PDK::Config::Validator.string
|
|
15
|
+
# default_to { false }
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
class Setting
|
|
19
|
+
attr_reader :namespace
|
|
20
|
+
|
|
21
|
+
# It is possible to have multiple setting definitions for the same setting; for example, defining a default value with a lambda, but the
|
|
22
|
+
# the validation is within a JSON schema document. These are expressed as two settings objects, and uses a single linked list to join them
|
|
23
|
+
# together:
|
|
24
|
+
#
|
|
25
|
+
# (PDK::Config::JSONSchemaSetting) --previous_setting--> (PDK::Config::Setting)
|
|
26
|
+
#
|
|
27
|
+
# So in the example above, calling `default` the on the first object in the list will:
|
|
28
|
+
# 1. Look at `default` on PDK::Config::JSONSchemaSetting
|
|
29
|
+
# 2. If a default could not be found then it calls `default` on previous_setting
|
|
30
|
+
# 3. If a default could not be found then it calls `default` on previous_setting.previous_setting
|
|
31
|
+
# 4. and so on down the linked list (chain) of settings
|
|
32
|
+
attr_writer :previous_setting
|
|
33
|
+
|
|
34
|
+
# Initialises an empty setting definition.
|
|
35
|
+
#
|
|
36
|
+
# @param name [String,Symbol] the name of the setting.
|
|
37
|
+
# @param namespace [PDK::Config::Namespace] The namespace this setting belongs to
|
|
38
|
+
def initialize(name, namespace, initial_value = nil)
|
|
39
|
+
@name = name.to_s
|
|
40
|
+
@validators = []
|
|
41
|
+
@namespace = namespace
|
|
42
|
+
@value = initial_value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def qualified_name
|
|
46
|
+
[namespace.name, @name].join('.')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def value
|
|
50
|
+
# Duplicate arrays and hashes so that they are isolated from changes being made
|
|
51
|
+
PDK::Util.deep_duplicate(@value)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def value=(obj)
|
|
55
|
+
validate!(obj)
|
|
56
|
+
@value = obj
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_s
|
|
60
|
+
@value.to_s
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Assign a validator to the setting. Subclasses should not override this method.
|
|
64
|
+
#
|
|
65
|
+
# @param validator [Hash{Symbol => [Proc,String]}]
|
|
66
|
+
# @option validator [Proc] :proc a lambda that takes the setting to be
|
|
67
|
+
# validated as the argument and returns `true` if the setting is valid.
|
|
68
|
+
# @option validator [String] :message a description of what the validator
|
|
69
|
+
# is testing for, that is displayed to the user as part of the error
|
|
70
|
+
# message for invalid settings.
|
|
71
|
+
#
|
|
72
|
+
# @raise [ArgumentError] if not passed a Hash.
|
|
73
|
+
# @raise [ArgumentError] if the Hash doesn't have a `:proc` key that
|
|
74
|
+
# contains a Proc.
|
|
75
|
+
# @raise [ArgumentError] if the Hash doesn't have a `:message` key that
|
|
76
|
+
# contains a String.
|
|
77
|
+
#
|
|
78
|
+
# @return [nil]
|
|
79
|
+
def validate(validator)
|
|
80
|
+
raise ArgumentError, '`validator` must be a Hash' unless validator.is_a?(Hash)
|
|
81
|
+
raise ArgumentError, 'the :proc key must contain a Proc' unless validator.key?(:proc) && validator[:proc].is_a?(Proc)
|
|
82
|
+
raise ArgumentError, 'the :message key must contain a String' unless validator.key?(:message) && validator[:message].is_a?(String)
|
|
83
|
+
|
|
84
|
+
@validators << validator
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Validate a setting against the assigned validators.
|
|
88
|
+
#
|
|
89
|
+
# @param setting [Object] the setting being validated.
|
|
90
|
+
#
|
|
91
|
+
# @raise [ArgumentError] if any of the assigned validators fail to
|
|
92
|
+
# validate the setting.
|
|
93
|
+
#
|
|
94
|
+
# @return [nil]
|
|
95
|
+
def validate!(value)
|
|
96
|
+
@validators.each do |validator|
|
|
97
|
+
next if validator[:proc].call(value)
|
|
98
|
+
|
|
99
|
+
raise ArgumentError, format('%{key} %{message}', key: qualified_name, message: validator[:message])
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Assign a default value proc for the setting. Subclasses should not override this method.
|
|
104
|
+
#
|
|
105
|
+
# @param block [Proc] a block that is lazy evaluated when necessary in
|
|
106
|
+
# order to determine the default setting.
|
|
107
|
+
#
|
|
108
|
+
# @return [nil]
|
|
109
|
+
def default_to(&block)
|
|
110
|
+
raise ArgumentError, 'must be passed a block' unless block
|
|
111
|
+
|
|
112
|
+
@default_to = block
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Evaluate the default setting.
|
|
116
|
+
#
|
|
117
|
+
# @return [Object,nil] the result of evaluating the block given to
|
|
118
|
+
# {#default_to}, or `nil` if the setting has no default.
|
|
119
|
+
def default
|
|
120
|
+
return @default_to.call if default_block?
|
|
121
|
+
|
|
122
|
+
# If there is a previous setting in the chain, use its default
|
|
123
|
+
@previous_setting&.default
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
# @return [Boolean] true if the setting has a default setting block. Subclasses should not override this method.
|
|
129
|
+
def default_block?
|
|
130
|
+
!@default_to.nil?
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|