-
Notifications
You must be signed in to change notification settings - Fork 5
/
yaml_settings.rb
198 lines (159 loc) · 5.11 KB
/
yaml_settings.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
Puppet::Type.newtype(:yaml_settings) do
file_param_validation = proc do
validate do |value|
unless Puppet::Util.absolute_path?(value)
fail Puppet::Error, "File paths must be fully qualified, not '#{value}'"
end
end
munge do |value|
File.join(File.split(File.expand_path(value)))
end
end
# parameters
newparam :name, namevar: true do
desc <<-'EOT'
An arbitrary name for the resource. It will be the default for 'target'.
EOT
end
newparam :target do
isrequired
desc <<-'EOT'
The path to the properties file to manage. Either this should file should
already exist, or the source parameter needs to be specified.
EOT
class_eval(&file_param_validation)
def default
@resource[:name]
end
end
newparam :source do
desc <<-'EOT'
The file from which to load the current settings. If unspecified, it
defaults to the target file.
EOT
class_eval(&file_param_validation)
end
# boolean: true generates an allow_new_values? method in the type
newparam :allow_new_values, boolean: true do
desc 'Whether it should be allowed to specify values for non-existing tree portions'
defaultto :true
newvalues :true, :false
end
newparam :allow_new_file, boolean: true do
desc 'Whether it should be allowed to create a new target file'
defaultto :true
newvalues :true, :false
end
newparam :user do
desc 'The user with which to make the changes'
end
# the property
newproperty :values do
isrequired
desc <<-'EOT'
The portions to change and their new values.
This should be a hash. The subtree to change is specified in the form:
<key 1>/<key 2>/.../<key n>
where <key x> admits three variants:
* the plain contents of the string key, as long as they do not start
with : or ' and do not contain /
* '<contents>', to represent a string key that contains the characters
mentioned above. Single quotes must be doubled to have literal value.
* :'<contents>', likewise, but the value will be a symbol.
EOT
# invalid value, but even with isrequired puppet doesn't require the
# property to be managed otherwise
defaultto :absent
def self.keys_regex
%r{(?:\A | /)
(?'key' [^:'][^/]* |
:?'(?: [^'] | '')+'
)
(?= / | \z)}x
end
def self.keys(path)
# match regex, transform keys
pos = 0
res = []
loop do
match = path.match(keys_regex, pos)
fail "could not match on position #{pos} the string: #{path}. Matched so far: #{res}" unless match
value = match['key']
if value.start_with?(':\'')
value = value[2..-2].gsub("''", "'").to_sym
elsif value.start_with?("'")
value = value[1..-2].gsub("''", "'")
end
res << value
break if match.end(0) == path.length
pos = match.end(0)
end
res
end
def self.convert_value(v)
return v.values.first.to_sym if
defined?(Puppet::Pops::Types::PEnumType) && v.instance_of?(Puppet::Pops::Types::PEnumType)
case v
when :undef then nil
when Array then v.map(&method(:convert_value))
when Hash then Hash[v.map { |k, inner_v| [convert_value(k), convert_value(inner_v)] }]
else v
end
end
# override so not to call munge/validate on each value array value individually if array
def should=(values)
@shouldorig = values
validate(values)
@should = [munge(values)]
end
validate do |value|
fail "Expected 'values' property to be a hash, " \
"got #{value} (class #{value.class})" unless value.instance_of? Hash
value.keys.each do |key|
fail "One of the keys of the 'values' hash is not a non-empty string: found: #{key.inspect}" unless
key.instance_of? String and !key.empty?
end
end
munge do |value|
res = value.each_with_object({}) do |(k, v), memo|
key_array = self.class.keys(k)
memo[key_array] = v
end
self.class.convert_value(res)
end
def retrieve
return :absent unless File.file?(@resource[:target])
provider.current_values
end
# rubocop:disable UnusedMethodArgument
def set(value)
@resource.refresh
end
# rubocop:enable UnusedMethodArgument
def insync?(is)
return false unless is.instance_of? Hash # e.g. :absent
# go over each one
result = true
should.each_pair do |key, desired_value|
cur_value = is.subtree_fetch key
unless cur_value == desired_value
Puppet.notice "Key #{key} is out of sync: '#{cur_value}' should be '#{desired_value}'"
result = false
end
end
result
end
end
# end properties / params
def refresh
provider.refresh
end
def autorequire(rel_catalog = nil)
reqs = super
rel_catalog ||= catalog
if self[:source] and (dep = rel_catalog.resource(:file, self[:source]))
reqs << Puppet::Relationship.new(dep, self, event: :ALL_EVENTS, callback: :refresh)
end
reqs
end
end