summaryrefslogtreecommitdiffstats
path: root/lib/puppet/provider/aixobject.rb
blob: 9506c67a2612d5e53c1329f615df11f260a7d1d8 (plain)
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