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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
|
#
# Common code for AIX providers. This class implements basic structure for
# AIX resources.
# Author:: Hector Rivas Gandara <keymon@gmail.com>
#
class Puppet::Provider::AixObject < Puppet::Provider
desc "Generic AIX resource provider"
# The real provider must implement these functions.
def lscmd(value=@resource[:name])
raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}"
end
def lscmd(value=@resource[:name])
raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}"
end
def addcmd(extra_attrs = [])
raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}"
end
def modifycmd(attributes_hash)
raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}"
end
def deletecmd
raise Puppet::Error, "Method not defined #{@resource.class.name} #{@resource.name}: #{detail}"
end
# Valid attributes to be managed by this provider.
# It is a list of hashes
# :aix_attr AIX command attribute name
# :puppet_prop Puppet propertie name
# :to Optional. Method name that adapts puppet property to aix command value.
# :from Optional. Method to adapt aix command line value to puppet property. Optional
class << self
attr_accessor :attribute_mapping
end
# Mapping from Puppet property to AIX attribute.
def self.attribute_mapping_to
if ! @attribute_mapping_to
@attribute_mapping_to = {}
attribute_mapping.each { |elem|
attribute_mapping_to[elem[:puppet_prop]] = {
:key => elem[:aix_attr],
:method => elem[:to]
}
}
end
@attribute_mapping_to
end
# Mapping from AIX attribute to Puppet property.
def self.attribute_mapping_from
if ! @attribute_mapping_from
@attribute_mapping_from = {}
attribute_mapping.each { |elem|
attribute_mapping_from[elem[:aix_attr]] = {
:key => elem[:puppet_prop],
:method => elem[:from]
}
}
end
@attribute_mapping_from
end
# This functions translates a key and value using the given mapping.
# Mapping can be nil (no translation) or a hash with this format
# {:key => new_key, :method => translate_method}
# It returns a list with the pair [key, value]
def translate_attr(key, value, mapping)
return [key, value] unless mapping
return nil unless mapping[key]
if mapping[key][:method]
new_value = method(mapping[key][:method]).call(value)
else
new_value = value
end
[mapping[key][:key], new_value]
end
# Loads an AIX attribute (key=value) and stores it in the given hash with
# puppet semantics. It translates the pair using the given mapping.
#
# This operation works with each property one by one,
# subclasses must reimplement this if more complex operations are needed
def load_attribute(key, value, mapping, objectinfo)
if mapping.nil?
objectinfo[key] = value
elsif mapping[key].nil?
# is not present in mapping, ignore it.
true
elsif mapping[key][:method].nil?
objectinfo[mapping[key][:key]] = value
elsif
objectinfo[mapping[key][:key]] = method(mapping[key][:method]).call(value)
end
return objectinfo
end
# Gets the given command line argument for the given key and value,
# using the given mapping to translate key and value.
# All the objectinfo hash (@resource or @property_hash) is passed.
#
# This operation works with each property one by one,
# and default behaviour is return the arguments as key=value pairs.
# Subclasses must reimplement this if more complex operations/arguments
# are needed
#
def get_arguments(key, value, mapping, objectinfo)
if mapping.nil?
new_key = key
new_value = value
elsif mapping[key].nil?
# is not present in mapping, ignore it.
new_key = nil
new_value = nil
elsif mapping[key][:method].nil?
new_key = mapping[key][:key]
new_value = value
elsif
new_key = mapping[key][:key]
new_value = method(mapping[key][:method]).call(value)
end
# convert it to string
new_value = Array(new_value).join(',')
if new_key
return [ "#{new_key}=#{new_value}" ]
else
return []
end
end
# Convert the provider properties (hash) to AIX command arguments
# (list of strings)
# This function will translate each value/key and generate the argument using
# the get_arguments function.
def hash2args(hash, mapping=self.class.attribute_mapping_to)
return "" unless hash
arg_list = []
hash.each {|key, val|
arg_list += self.get_arguments(key, val, mapping, hash)
}
arg_list
end
# Parse AIX command attributes from the output of an AIX command, that
# which format is a list of space separated of key=value pairs:
# "uid=100 groups=a,b,c".
# It returns an hash.
#
# If a mapping is provided, the keys are translated as defined in the
# mapping hash. And only values included in mapping will be added
#
# NOTE: it will ignore the items not including '='
def parse_attr_list(str, mapping=self.class.attribute_mapping_from)
properties = {}
attrs = []
if !str or (attrs = str.split()).empty?
return nil
end
attrs.each { |i|
if i.include? "=" # Ignore if it does not include '='
(key_str, val) = i.split('=')
# Check the key
if !key_str or key_str.empty?
info "Empty key in string 'i'?"
continue
end
key = key_str.to_sym
properties = self.load_attribute(key, val, mapping, properties)
end
}
properties.empty? ? nil : properties
end
# Parse AIX command output in a colon separated list of attributes,
# This function is useful to parse the output of commands like lsfs -c:
# #MountPoint:Device:Vfs:Nodename:Type:Size:Options:AutoMount:Acct
# /:/dev/hd4:jfs2::bootfs:557056:rw:yes:no
# /home:/dev/hd1:jfs2:::2129920:rw:yes:no
# /usr:/dev/hd2:jfs2::bootfs:9797632:rw:yes:no
#
# If a mapping is provided, the keys are translated as defined in the
# mapping hash. And only values included in mapping will be added
def parse_colon_list(str, key_list, mapping=self.class.attribute_mapping_from)
properties = {}
attrs = []
if !str or (attrs = str.split(':')).empty?
return nil
end
attrs.each { |val|
key = key_list.shift.downcase.to_sym
properties = self.load_attribute(key, val, mapping, properties)
}
properties.empty? ? nil : properties
end
# Default parsing function for AIX commands.
# It will choose the method depending of the first line.
# For the colon separated list it will:
# 1. Get keys from first line.
# 2. Parse next line.
def parse_command_output(output, mapping=self.class.attribute_mapping_from)
lines = output.split("\n")
# if it begins with #something:... is a colon separated list.
if lines[0] =~ /^#.*:/
self.parse_colon_list(lines[1], lines[0][1..-1].split(':'), mapping)
else
self.parse_attr_list(lines[0], mapping)
end
end
# Retrieve all the information of an existing resource.
# It will execute 'lscmd' command and parse the output, using the mapping
# 'attribute_mapping_from' to translate the keys and values.
def getinfo(refresh = false)
if @objectinfo.nil? or refresh == true
# Execute lsuser, split all attributes and add them to a dict.
begin
output = execute(self.lscmd)
@objectinfo = self.parse_command_output(execute(self.lscmd))
# All attributtes without translation
@objectosinfo = self.parse_command_output(execute(self.lscmd), nil)
rescue Puppet::ExecutionFailure => detail
# Print error if needed. FIXME: Do not check the user here.
Puppet.debug "aix.getinfo(): Could not find #{@resource.class.name} #{@resource.name}: #{detail}"
end
end
@objectinfo
end
# Like getinfo, but it will not use the mapping to translate the keys and values.
# It might be usefult to retrieve some raw information.
def getosinfo(refresh = false)
if @objectosinfo .nil? or refresh == true
getinfo(refresh)
end
@objectosinfo
end
# List all elements of given type. It works for colon separated commands and
# list commands.
# It returns a list of names.
def list_all
names = []
begin
output = execute(self.lsallcmd()).split('\n')
(output.select{ |l| l != /^#/ }).each { |v|
name = v.split(/[ :]/)
names << name if not name.empty?
}
rescue Puppet::ExecutionFailure => detail
# Print error if needed
Puppet.debug "aix.list_all(): Could not get all resources of type #{@resource.class.name}: #{detail}"
end
names
end
#-------------
# Provider API
# ------------
# Clear out the cached values.
def flush
@property_hash.clear if @property_hash
@objectinfo.clear if @objectinfo
end
# Check that the user exists
def exists?
!!getinfo(true) # !! => converts to bool
end
# Return all existing instances
# The method for returning a list of provider instances. Note that it returns
# providers, preferably with values already filled in, not resources.
def self.instances
objects=[]
self.list_all().each { |entry|
objects << new(:name => entry, :ensure => :present)
}
objects
end
#- **ensure**
# The basic state that the object should be in. Valid values are
# `present`, `absent`, `role`.
# From ensurable: exists?, create, delete
def ensure
if exists?
:present
else
:absent
end
end
# Create a new instance of the resource
def create
if exists?
info "already exists"
# The object already exists
return nil
end
begin
execute(self.addcmd)
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not create #{@resource.class.name} #{@resource.name}: #{detail}"
end
end
# Delete this instance of the resource
def delete
unless exists?
info "already absent"
# the object already doesn't exist
return nil
end
begin
execute(self.deletecmd)
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not delete #{@resource.class.name} #{@resource.name}: #{detail}"
end
end
#--------------------------------
# Call this method when the object is initialized.
# It creates getter/setter methods for each property our resource type supports.
# If setter or getter already defined it will not be overwritten
def self.mk_resource_methods
[resource_type.validproperties, resource_type.parameters].flatten.each do |prop|
next if prop == :ensure
define_method(prop) { get(prop) || :absent} unless public_method_defined?(prop)
define_method(prop.to_s + "=") { |*vals| set(prop, *vals) } unless public_method_defined?(prop.to_s + "=")
end
end
# Define the needed getters and setters as soon as we know the resource type
def self.resource_type=(resource_type)
super
mk_resource_methods
end
# Retrieve a specific value by name.
def get(param)
(hash = getinfo(false)) ? hash[param] : nil
end
# Set a property.
def set(param, value)
@property_hash[symbolize(param)] = value
if getinfo().nil?
# This is weird...
raise Puppet::Error, "Trying to update parameter '#{param}' to '#{value}' for a resource that does not exists #{@resource.class.name} #{@resource.name}: #{detail}"
end
if value == getinfo()[param.to_sym]
return
end
#self.class.validate(param, value)
if cmd = modifycmd({param =>value})
begin
execute(cmd)
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}"
end
end
# Refresh de info.
hash = getinfo(true)
end
def initialize(resource)
super
@objectinfo = nil
@objectosinfo = nil
end
end
|