forj 0.0.48 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1687 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # (c) Copyright 2014 Hewlett-Packard Development Company, L.P.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+
19
+ # Those classes describes :
20
+ # - processes (BaseProcess) : How to create/delete/edit/query object.
21
+ # - controler (BaseControler) : If a provider is defined, define how will do object creation/etc...
22
+ # - definition(BaseDefinition): Functions to declare objects, query/data mapping and setup
23
+ # this task to make it to work.
24
+
25
+ module ForjLib
26
+ def ForjLib::debug(iLevel, sMsg)
27
+ if iLevel <= $LIB_FORJ_DEBUG
28
+ Logging.debug("-%s- %s" % [iLevel, sMsg])
29
+ end
30
+ end
31
+ end
32
+
33
+ module ForjLib
34
+ class ForjLib::Data
35
+
36
+ def initialize(oType = :object)
37
+ # Support :data for single object data
38
+ # :list for a list of object data
39
+ oType = :data if not [:list, :object, :data].include?(oType)
40
+ @oType = oType
41
+ case oType
42
+ when :data, :object
43
+ @data = new_object
44
+ when :list
45
+ @data = new_object_list
46
+ end
47
+ end
48
+
49
+ def type?()
50
+ @oType
51
+ end
52
+
53
+ def object_type?()
54
+ @data[:object_type]
55
+ end
56
+
57
+ def set(oObj, sObjType = nil, hQuery = {})
58
+ if oObj.is_a?(ForjLib::Data)
59
+ oType = oObj.type?
60
+ case oType
61
+ when :data, :object
62
+ @data[:object_type] = oObj.object_type?
63
+ @data[:object] = oObj.get(:object)
64
+ @data[:attrs] = oObj.get(:attrs)
65
+ when :list
66
+ @data[:object_type] = oObj.object_type?
67
+ @data[:object] = oObj.get(:object)
68
+ @data[:list] = oObj.get(:list)
69
+ @data[:query] = oObj.get(:query)
70
+ end
71
+ return self
72
+ end
73
+
74
+ # while saving the object, a mapping work is done?
75
+ case @oType
76
+ when :data, :object
77
+ @data[:object_type] = sObjType
78
+ @data[:object] = oObj
79
+ @data[:attrs] = yield(sObjType, oObj)
80
+ when :list
81
+ @data[:object] = oObj
82
+ @data[:object_type] = sObjType
83
+ @data[:query] = hQuery
84
+ unless oObj.nil?
85
+ begin
86
+ oObj.each { | oObject |
87
+ next if oObject.nil?
88
+ begin
89
+ oDataObject = ForjLib::Data.new(:object)
90
+
91
+ oDataObject.set(oObject, sObjType) { |sObjectType, oObject|
92
+ yield(sObjectType, oObject)
93
+ }
94
+ @data[:list] << oDataObject
95
+ rescue => e
96
+ raise ForjError.new(), "'%s' Mapping attributes issue.\n%s" % [sObjType, e.message]
97
+ end
98
+ }
99
+ rescue => e
100
+ raise ForjError.new(), "each function is not supported by '%s'.\n%s" % [oObj.class, e.message]
101
+ end
102
+ end
103
+ end
104
+ self
105
+ end
106
+
107
+ def [](*key)
108
+ get(*key)
109
+ end
110
+
111
+ def []=(*key, value)
112
+ return false if @oType == :list
113
+ rhSet(@data, value, :attrs, key)
114
+ true
115
+ end
116
+
117
+ def get(*key)
118
+ return @data if key.length == 0
119
+ case @oType
120
+ when :data, :object # Return only attrs or the real object.
121
+ return @data[key[0]] if key[0] == :object
122
+ return rhGet(@data, key) if key[0] == :attrs
123
+ rhGet(@data, :attrs, key)
124
+ when :list
125
+ return @data[key[0]] if [:object, :query].include?(key[0])
126
+ return @data[:list][key[0]] if key.length == 1
127
+ @data[:list][key[0]][key[1..-1]] # can Return only attrs or the real object.
128
+ end
129
+ end
130
+
131
+ def exist?(*key)
132
+ case @oType
133
+ when :data, :object
134
+ return true if key[0] == :object and @data.key?(key[0])
135
+ return true if key[0] == :attrs and rhExist?(@data, key)
136
+ (rhExist?(@data, :attrs, key) == key.length+1)
137
+ when :list
138
+ return true if key[0] == :object and @data.key?(key[0])
139
+ (rhExist?(@data[:list][key[0]], :attrs, key[1..-1]) == key.length)
140
+ end
141
+ end
142
+
143
+ def nil?()
144
+ @data[:object].nil?
145
+ end
146
+
147
+ def length()
148
+ case @oType
149
+ when :data
150
+ return 0 if self.nil?
151
+ 1
152
+ when :list
153
+ @data[:list].length
154
+ end
155
+ end
156
+
157
+ def each(sData = :list)
158
+ to_remove = []
159
+ return nil if @oType != :list or not [:object, :list].include?(sData)
160
+
161
+ @data[:list].each { |elem|
162
+ sAction = yield (elem)
163
+ case sAction
164
+ when :remove
165
+ to_remove << elem
166
+ end
167
+ }
168
+ if to_remove.length > 0
169
+ to_remove.each { | elem |
170
+ @data[:list].delete(elem)
171
+ }
172
+ end
173
+ end
174
+
175
+ def each_index(sData = :list)
176
+ to_remove = []
177
+ return nil if @oType != :list or not [:object, :list].include?(sData)
178
+
179
+ @data[:list].each_index { |iIndex|
180
+ sAction = yield (iIndex)
181
+ case sAction
182
+ when :remove
183
+ to_remove << @data[:list][iIndex]
184
+ end
185
+ }
186
+ if to_remove.length > 0
187
+ to_remove.each { | elem |
188
+ @data[:list].delete(elem)
189
+ }
190
+ end
191
+ end
192
+
193
+ def registered?()
194
+ @bRegister
195
+ end
196
+
197
+ def register()
198
+ @bRegister = true
199
+ self
200
+ end
201
+
202
+ def unregister()
203
+ @bRegister = false
204
+ self
205
+ end
206
+ private
207
+
208
+ def new_object_list
209
+ {
210
+ :object => nil,
211
+ :object_type => nil,
212
+ :list => [],
213
+ :query => nil
214
+ }
215
+ end
216
+
217
+ def new_object
218
+ oCoreObject = {
219
+ :object_type => nil,
220
+ :attrs => {},
221
+ :object => nil,
222
+ }
223
+ end
224
+
225
+ end
226
+ end
227
+
228
+ class ForjError < RuntimeError
229
+ attr_reader :ForjMsg
230
+
231
+ def initialize(message = nil)
232
+ @ForjMsg = message
233
+ end
234
+ end
235
+
236
+ # Class to handle key or keypath on needs
237
+ class KeyPath
238
+
239
+ def initialize(sKeyPath = nil)
240
+
241
+ @keypath = []
242
+ self.set sKeyPath
243
+ end
244
+
245
+ def key=(sKeyPath)
246
+ self.set(sKeyPath)
247
+ end
248
+
249
+ def set(sKeyPath)
250
+
251
+ if sKeyPath.is_a?(Symbol)
252
+ @keypath = [ sKeyPath]
253
+ elsif sKeyPath.is_a?(Array)
254
+ @keypath = sKeyPath
255
+ elsif sKeyPath.is_a?(String)
256
+ if /[^\\\/]?\/[^\/]/ =~ sKeyPath or /:[^:\/]/ =~ sKeyPath
257
+ # keypath to interpret
258
+ aResult = sKeyPath.split('/')
259
+ aResult.each_index { | iIndex |
260
+ next if not aResult[iIndex].is_a?(String)
261
+ aResult[iIndex] = aResult[iIndex][1..-1].to_sym if aResult[iIndex][0] == ":"
262
+ }
263
+ @keypath = aResult
264
+ else
265
+ @keypath = [sKeyPath]
266
+ end
267
+ end
268
+ end
269
+
270
+ def aTree()
271
+ @keypath
272
+ end
273
+
274
+ def sFullPath()
275
+ return nil if @keypath.length == 0
276
+ aKeyAccess = @keypath.clone
277
+ aKeyAccess.each_index { |iIndex|
278
+ next if not aKeyAccess[iIndex].is_a?(Symbol)
279
+ aKeyAccess[iIndex] = ":" + aKeyAccess[iIndex].to_s
280
+ }
281
+ aKeyAccess.join('/')
282
+ end
283
+
284
+ def to_s
285
+ return nil if @keypath.length == 0
286
+ aKeyAccess = @keypath.clone
287
+ aKeyAccess.each_index { |iIndex|
288
+ next if not aKeyAccess[iIndex].is_a?(Symbol)
289
+ aKeyAccess[iIndex] = aKeyAccess[iIndex].to_s
290
+ }
291
+ aKeyAccess.join('/')
292
+ end
293
+
294
+ def sKey(iIndex = -1)
295
+ return nil if @keypath.length == 0
296
+ @keypath[iIndex] if self.length >= 1
297
+ end
298
+
299
+ def length()
300
+ @keypath.length
301
+ end
302
+ end
303
+
304
+ # This is the main class definition.
305
+ # It drives creation of High level cloud class object, like servers
306
+ # Initialization requires a Configuration Object (ForjConfig) and the Account to load.
307
+ # Account is loaded with ForjAccount Object.
308
+ # During ForjCloud initialization, general options + account options are loaded.
309
+
310
+ # For example, to create a server
311
+
312
+ # oCloud = ForjCloud.new(oConfig, 'myhpcloud')
313
+ # oConfig.set(:server_name,'myservername')
314
+ # oCloud.Create(:server)
315
+
316
+ # Most of data are predefined from account or general config.
317
+ # If some required value are missing, an error is reported.
318
+ # A Process Object can be defined, in order to add some process features, like :maestro_server
319
+
320
+ # Based Forj Object to use, with a process and (or not) a controler.
321
+ class ForjObject
322
+
323
+ # ForjObject parameters are:
324
+ # oForjConfig : Required. an instance of a configuration system which HAVE to provide
325
+ # 2 kind of functions:
326
+ # - set (key, value) and []=(key, value)
327
+ # From processes, you can set a runtime data with:
328
+ # config.set(key, value)
329
+ # OR
330
+ # config[key] = value
331
+ #
332
+ # - get (key, default) and [](key, default)
333
+ # default is an optional value.
334
+ # From processes, you can get a data (runtime/account/config.yaml or defaults.yaml) with:
335
+ # config.get(key)
336
+ # OR
337
+ # config[key]
338
+
339
+ # sProcessClass: Required. string or symbol. Is the name of ProcessClass to use.
340
+ # This class is dynamically loaded and derived from BaseProcess class.
341
+ # It loads the Process class content from a file '$CORE_PROCESS_PATH/<sProcessClass>.rb'
342
+ # If sProcessClass is a file path, this file will be loaded as a ruby include.
343
+
344
+ # <sProcessClass>.rb file name is case sensible and respect RUBY Class name convention
345
+
346
+ # sControllerClass: Optional. string or symbol. Is the name of ControlerClass to use.
347
+ # This class is dynamically loaded and derived from BaseControler class.
348
+ # It loads the Controler class content from a file '$PROVIDER_PATH/<sControlerClass>.rb'
349
+ #
350
+ # The provider can redefine partially or totally some processes
351
+ # ForjObject will load those redefinition from file:
352
+ # '$PROVIDER_PATH/<sControlerClass>Process.rb'
353
+
354
+ # <sControllerClass>.rb or <sControllerClass>Process.rb file name is case sensible and respect RUBY Class name convention
355
+
356
+ attr_reader :config
357
+
358
+
359
+ def initialize(oForjConfig, processesClass = nil, sControllerClass = nil)
360
+ # Loading ProcessClass
361
+ # Create Process derived from respectively BaseProcess
362
+ @config = oForjConfig
363
+
364
+ if processesClass.nil?
365
+ aProcessesClass = []
366
+ elsif not processesClass.is_a?(Array)
367
+ aProcessesClass = [processesClass]
368
+ else
369
+ aProcessesClass = processesClass
370
+ end
371
+
372
+ cBaseProcess = BaseProcess
373
+ cProcessClass = nil
374
+
375
+ aProcessesClass.each { | sProcessClass |
376
+ ForjLib.debug(1, "Loading Process '%s'" % sProcessClass)
377
+
378
+ # And load the content from the <sProcessClass>.rb
379
+ if sProcessClass.is_a?(Symbol)
380
+ sFile = File.join($CORE_PROCESS_PATH, sProcessClass.to_s + '.rb')
381
+ else
382
+ if sProcessClass.include?('/')
383
+ # Consider a path to the process file. File name is the name of the class.
384
+ sPath = File.dirname(File.expand_path(sProcessClass))
385
+ file = File.basename(sProcessClass)
386
+ file['.rb'] = '' if file['.rb']
387
+ sProcessClass = file
388
+ sProcessClass = file.capitalize if (/[A-Z]/ =~ file) != 0
389
+ else
390
+ sPath = $CORE_PROCESS_PATH
391
+ end
392
+ sFile = File.join(sPath, sProcessClass + '.rb')
393
+ end
394
+ if File.exists?(sFile)
395
+ cNewClass = Class.new(cBaseProcess)
396
+ sProcessClass = "%sProcess" % sProcessClass if not /Process$/ =~ sProcessClass
397
+ ForjLib.debug(1, "Declaring Process '%s'" % sProcessClass)
398
+ cBaseProcess = Object.const_set(sProcessClass, cNewClass)
399
+ cProcessClass = sProcessClass
400
+ BaseDefinition.current_process(cBaseProcess)
401
+ load sFile
402
+ else
403
+ Logging.warning("Process file definition '%s' is missing. " % sFile)
404
+ end
405
+ }
406
+
407
+ if sControllerClass
408
+ ForjLib.debug(1, "Loading Controler/definition '%s'" % sControllerClass)
409
+ # Add Provider Object -------------
410
+ sProviderClass = sControllerClass.capitalize
411
+
412
+ # Initialize an empty class derived from BaseDefinition.
413
+ # This to ensure provider Class will be derived from this Base Class
414
+ # If this class is derived from a different Class, ruby will raise an error.
415
+
416
+ # Create Definition and Controler derived from respectively BaseDefinition and BaseControler
417
+ cBaseDefinition = Class.new(BaseDefinition)
418
+ # Finally, name that class!
419
+ Object.const_set sProviderClass, cBaseDefinition
420
+
421
+ cBaseControler = Class.new(BaseController)
422
+ Object.const_set sProviderClass + 'Controller', cBaseControler
423
+
424
+ # Loading Provider base file. This file should load a class
425
+ # which have the same name as the file.
426
+ if sControllerClass.include?('/')
427
+ # Consider a path to the process file. File name is the name of the class.
428
+ sPath = File.dirname(File.expand_path(sControllerClass))
429
+ file = File.basename(sControllerClass)
430
+ file['.rb'] = '' if file['.rb']
431
+ sControllerClass = file
432
+ else
433
+ sPath = File.join($PROVIDERS_PATH, sControllerClass)
434
+ end
435
+ sFile = File.join(sPath, sProviderClass + '.rb')
436
+ if File.exists?(sFile)
437
+ load sFile
438
+ else
439
+ raise ForjError.new(), "Provider file definition '%s' is missing. Cannot go on" % sFile
440
+ end
441
+
442
+ #cForjBaseCloud = Class.new(ForjBaseCloud)
443
+ # Finally, name that class!
444
+ #Object.const_set sProviderClass, cForjBaseCloud
445
+
446
+ # Identify Provider Classes. Search for
447
+ # - Definition Class (sProviderClass) - Contains ForjClass Object
448
+ # - Controller Class (sProviderClass + 'Controller') - Provider Cloud controler object
449
+
450
+ # Search for Definition Class
451
+ begin
452
+ # Get it from Objects
453
+ oDefClass = Object.const_get(sProviderClass)
454
+ rescue
455
+ raise ForjError.new(), 'ForjCloud: Unable to find class "%s"' % sProviderClass
456
+ end
457
+
458
+ # Search for Controler Class
459
+ # - Process Class (sProviderClass + 'Process') - Provider Process object if defined
460
+ begin
461
+ # Get the same one suffixed with 'Provider' from Objects
462
+ oCoreObjectControllerClass = Object.const_get(sProviderClass + 'Controller')
463
+ rescue
464
+ raise ForjError.new(), 'ForjCloud: Unable to find class "%s"' % sProviderClass + 'Controller'
465
+ end
466
+
467
+ # Then, we create an BaseCloud Object with 2 objects joined:
468
+ # ForjAccount and a BaseControler Object type
469
+
470
+
471
+ else
472
+ oCoreObjectControllerClass = nil
473
+ end
474
+
475
+ # Add Process management object ---------------
476
+ unless cProcessClass.nil?
477
+ begin
478
+ oBaseProcessDefClass = Object.const_get(cProcessClass)
479
+ rescue
480
+ raise ForjError.new(), 'ForjCloud: Unable to find class "%s"' % cProcessClass
481
+ end
482
+ else
483
+ raise ForjError.new(), 'ForjCloud: No valid process loaded. Aborting.'
484
+ end
485
+ # Ex: Hpcloud(ForjAccount, HpcloudProvider)
486
+ if oCoreObjectControllerClass
487
+ @oCoreObject = oDefClass.new(oForjConfig, oBaseProcessDefClass.new(), oCoreObjectControllerClass.new())
488
+ else
489
+ @oCoreObject = oDefClass.new(oForjConfig, oBaseProcessDefClass.new())
490
+ end
491
+
492
+ end
493
+
494
+ def Connect(oCloudObj)
495
+ return nil if not oCloudObj or not @oCoreObject
496
+ @oCoreObject.Connect(oCloudObj)
497
+ end
498
+
499
+ def Create(oCloudObj)
500
+ return nil if not oCloudObj or not @oCoreObject
501
+ @oCoreObject.Create(oCloudObj)
502
+ end
503
+
504
+ def Delete(oCloudObj)
505
+ return nil if not oCloudObj or not @oCoreObject
506
+
507
+ @oCoreObject.Delete(oCloudObj)
508
+ end
509
+
510
+ def Query(oCloudObj, sQuery)
511
+ return nil if not oCloudObj or not @oCoreObject
512
+
513
+ @oCoreObject.Query(oCloudObj, sQuery)
514
+ end
515
+
516
+ def Get(oCloudObj, sId)
517
+ return nil if not oCloudObj or not @oCoreObject or sId.nil?
518
+
519
+ @oCoreObject.Get(oCloudObj, sId)
520
+ end
521
+
522
+ def Update(oCloudObj)
523
+ return nil if not oCloudObj or not @oCoreObject
524
+
525
+ @oCoreObject.Update(oCloudObj)
526
+ end
527
+
528
+ # Function used to ask users about setting up his account.
529
+ def Setup(oCloudObj, sAccountName = nil)
530
+ return nil if not oCloudObj or not @oCoreObject
531
+ @oCoreObject.Setup(oCloudObj, sAccountName)
532
+ end
533
+ end
534
+
535
+ # This class based on generic ForjObject, defines a Cloud Process to use.
536
+ class ForjCloud < ForjObject
537
+ def initialize(oConfig, sAccount = nil, aProcesses = [])
538
+
539
+ unless oConfig.is_a?(ForjAccount)
540
+ oForjAccount = ForjAccount.new(oConfig)
541
+ unless sAccount.nil?
542
+ oForjAccount.ac_load(sAccount)
543
+ end
544
+ else
545
+ oForjAccount = oConfig
546
+ end
547
+ aProcessList = [:CloudProcess]
548
+
549
+ sControllerMod = oForjAccount.get(:provider_name)
550
+ raise ForjError.new(), "Provider_name not set. Unable to create instance ForjCloud." if sControllerMod.nil?
551
+
552
+ sControllerProcessMod = File.join($PROVIDERS_PATH, sControllerMod, sControllerMod.capitalize + "Process.rb")
553
+ if File.exist?(sControllerProcessMod)
554
+ aProcessList << sControllerProcessMod
555
+ else
556
+ ForjLib.debug(1, "No Provider process defined. File '%s' not found." % sControllerProcessMod)
557
+ end
558
+
559
+ super(oForjAccount, aProcessList.concat(aProcesses), sControllerMod)
560
+ end
561
+ end
562
+
563
+
564
+ # class describing generic Object Process
565
+ # Ex: How to get a Network Object (ie: get a network or create it if missing)
566
+ class BaseProcess
567
+ def initialize()
568
+ @oDefinition = nil
569
+ end
570
+
571
+ def set_BaseObject(oDefinition)
572
+ @oDefinition = oDefinition
573
+ end
574
+ private
575
+
576
+ def controler
577
+ raise ForjError.new(), "No Controler object loaded." if not @oDefinition
578
+ @oDefinition
579
+ end
580
+
581
+ def object
582
+ raise ForjError.new(), "No Base object loaded." if not @oDefinition
583
+ @oDefinition
584
+ end
585
+
586
+ def format_object(sObjectType, oMiscObj)
587
+
588
+ raise ForjError.new(), "No Base object loaded." if not @oDefinition
589
+ @oDefinition.format_object(sObjectType, oMiscObj)
590
+ end
591
+
592
+ def format_query(sObjectType, oMiscObj, hQuery)
593
+
594
+ raise ForjError.new(), "No Base object loaded." if not @oDefinition
595
+ @oDefinition.format_list(sObjectType, oMiscObj, hQuery)
596
+ end
597
+
598
+ def get_data(oObj, *key)
599
+
600
+ raise ForjError.new(), "No Base object loaded." if not @oDefinition
601
+ @oDefinition.get_data(oObj, :attrs, key)
602
+ end
603
+
604
+ def register(oObject, sObjectType = nil)
605
+
606
+ raise ForjError.new(), "No Base object loaded." if not @oDefinition
607
+ @oDefinition.register(oObject, sObjectType)
608
+ end
609
+
610
+ def config
611
+ raise ForjError.new(), "No Base object loaded." if not @oDefinition
612
+ @oDefinition.config
613
+ end
614
+
615
+ def query_single(sCloudObj, oList, sQuery, name, sInfoMsg = {})
616
+ oList = controler.query(sCloudObj, sQuery)
617
+ sInfo = {
618
+ :notfound => "No %s '%s' found",
619
+ :checkmatch => "Found 1 %s. checking exact match for '%s'.",
620
+ :nomatch => "No %s '%s' match",
621
+ :found => "Found %s '%s'.",
622
+ :more => "Found several %s. Searching for '%s'.",
623
+ :items_form => "%s",
624
+ :items => [:name]
625
+ }
626
+ sInfo[:notfound] = sInfoMsg[:notfound] if sInfoMsg.key?(:notfound)
627
+ sInfo[:checkmatch] = sInfoMsg[:checkmatch] if sInfoMsg.key?(:checkmatch)
628
+ sInfo[:nomatch] = sInfoMsg[:nomatch] if sInfoMsg.key?(:nomatch)
629
+ sInfo[:found] = sInfoMsg[:found] if sInfoMsg.key?(:found)
630
+ sInfo[:more] = sInfoMsg[:more] if sInfoMsg.key?(:more)
631
+ sInfo[:items] = sInfoMsg[:items] if sInfoMsg.key?(:items)
632
+ sInfo[:items_form] = sInfoMsg[:items_form] if sInfoMsg.key?(:items_form)
633
+ case oList.length()
634
+ when 0
635
+ Logging.info( sInfo[:notfound] % [sCloudObj, name] )
636
+ oList
637
+ when 1
638
+ ForjLib.debug(2, sInfo[:checkmatch] % [sCloudObj, name])
639
+ element = nil
640
+ oList.each { | oElem |
641
+ bFound = true
642
+ sQuery.each { | key, value |
643
+ if oElem[key] != value
644
+ bFound = false
645
+ break
646
+ end
647
+ }
648
+ :remove if not bFound
649
+ }
650
+ if oList.length == 0
651
+ Logging.info(sInfo[:nomatch] % [sCloudObj, name])
652
+ else
653
+ sItems = []
654
+ if sInfo[:items].is_a?(Array)
655
+ sInfo[:items].each { | key |
656
+ sItems << oList[0, key]
657
+ }
658
+ else
659
+ sItems << oList[0, sInfo[:items]]
660
+ end
661
+ sItem = sInfo[:items_form] % sItems
662
+ Logging.info(sInfo[:found] % [sCloudObj, sItem])
663
+ end
664
+ oList
665
+ else
666
+ ForjLib.debug(2, sInfo[:more] % [sCloudObj, name])
667
+ # Looping to find the one corresponding
668
+ element = nil
669
+ oList.each { | oElem |
670
+ bFound = true
671
+ sQuery.each { | key, value |
672
+ if oElem[key] != value
673
+ bFound = false
674
+ break
675
+ end
676
+ }
677
+ :remove if not bFound
678
+ }
679
+ if oList.length == 0
680
+ Logging.info(sInfo[:notfound] % [sCloudObj, name])
681
+ else
682
+ sItems = []
683
+ if sInfo[:items].is_a?(Array)
684
+ sInfo[:items].each { | key |
685
+ sItems << oList[0, key]
686
+ }
687
+ else
688
+ sItems << oList[0, sInfo[:items]]
689
+ end
690
+ sItem = sInfo[:items_form] % sItems
691
+ Logging.info(sInfo[:found] % [sCloudObj, sItem])
692
+ end
693
+ oList
694
+ end
695
+ end
696
+ end
697
+
698
+
699
+ class BaseController
700
+ # Default handlers which needs to be defined by the cloud provider,
701
+ # called by BaseDefinition Create, Delete, Get, Query and Update functions.
702
+ def connect(sObjectType, hParams)
703
+ raise ForjError.new(), "connect has not been redefined by the provider '%s'" % self.class
704
+ end
705
+
706
+ def create(sObjectType, hParams)
707
+ raise ForjError.new(), "create_object has not been redefined by the provider '%s'" % self.class
708
+ end
709
+
710
+ def delete(sObjectType, hParams)
711
+ raise ForjError.new(), "delete_object has not been redefined by the provider '%s'" % self.class
712
+ end
713
+
714
+ def get(sObjectType, sUniqId, hParams)
715
+ raise ForjError.new(), "get_object has not been redefined by the provider '%s'" % self.class
716
+ end
717
+
718
+ def query(sObjectType, sQuery, hParams)
719
+ raise ForjError.new(), "query_object has not been redefined by the provider '%s'" % self.class
720
+ end
721
+
722
+ def update(sObjectType, hParams)
723
+ raise ForjError.new(), "update_object has not been redefined by the provider '%s'" % self.class
724
+ end
725
+
726
+ def forjError(msg)
727
+ raise ForjError.new(), "%s: %s" % [self.class, msg]
728
+ end
729
+
730
+ def required?(oParams, key)
731
+ raise ForjError.new(), "%s: %s is not set." % [self.class, key] if not oParams.exist?(key)
732
+ end
733
+ end
734
+
735
+ # represent an object or a list of object
736
+
737
+ # Collection of DataObject
738
+ # This class represents Object Parameters
739
+ class ObjectData
740
+ def initialize(bInternal = false)
741
+
742
+ @hParams = {}
743
+ @hParams[:hdata] = {} unless bInternal
744
+ @bInternal = bInternal
745
+ end
746
+
747
+ def [] (*key)
748
+
749
+ key = key.flatten
750
+ # Return ObjectData Element if asked. Ignore additional keys.
751
+ return @hParams[key[0]] if key[1] == :ObjectData
752
+
753
+ return @hParams if key.length == 0
754
+
755
+ oObject = rhGet(@hParams, key[0])
756
+ return nil if oObject.nil?
757
+
758
+ # Return attributes if asked
759
+ return oObject[:attrs, key[2..-1]] if key[1] == :attrs
760
+
761
+ if oObject.is_a?(ForjLib::Data)
762
+ if @bInternal
763
+ # params are retrieved in process context
764
+ # By default, if key is detected as a framework object, return its data.
765
+ return oObject[:attrs, key[1..-1]]
766
+ else
767
+ # params are retrieved in controller context
768
+ # By default, if key is detected as a controller object, return its data.
769
+ return oObject[:object, key[1..-1]]
770
+ end
771
+ end
772
+
773
+ # otherwise, simply return what is found in keys hierarchy.
774
+ rhGet(@hParams, key)
775
+ end
776
+
777
+ # Functions used to set simple data/Object for controller/process function call.
778
+ # TODO: to revisit this function, as we may consider simple data, as ForjLib::Data object
779
+ def []= (*key, value)
780
+ return nil if [:object, :query].include?(key[0])
781
+ rhSet(@hParams, value, key)
782
+ end
783
+
784
+ def add(oDataObject)
785
+ # Requires to be a valid framework object.
786
+ raise ForjError.new, "Invalid Framework object type '%s'." % oDataObject.class unless oDataObject.is_a?(ForjLib::Data)
787
+
788
+ sObjectType = oDataObject.object_type?
789
+
790
+ if oDataObject.type? == :list
791
+ oOldDataObject = rhGet(@hParams, :query, sObjectType)
792
+ oOldDataObject.unregister if oOldDataObject
793
+ rhSet(@hParams, oDataObject, :query, sObjectType)
794
+ else
795
+ oOldDataObject = rhGet(@hParams, sObjectType)
796
+ oOldDataObject.unregister if oOldDataObject
797
+ @hParams[sObjectType] = oDataObject
798
+ end
799
+ oDataObject.register
800
+ end
801
+
802
+ def delete(oObj)
803
+ if oObject.is_a?(Symbol)
804
+ sObjectType = oObj
805
+ @hParams[sObjectType] = nil
806
+ else
807
+ raise ForjError.new(), "ObjectData: delete error. oObj is not a symbol or a recognized formatted Object." unless oObj.key?(:object)
808
+ if oObj[:object_type] == :object_list
809
+ rhSet(@hParams, nil, :list, sObjectType)
810
+ else
811
+ sObjectType = oObj[:object_type]
812
+ @hParams[sObjectType] = nil
813
+ end
814
+ end
815
+ oObj.unregister
816
+ end
817
+
818
+ def << (hHash)
819
+ @hParams.merge!(hHash)
820
+ end
821
+
822
+ def exist?(*key)
823
+ raise ForjError.new, "ObjectData: key is not list of values (string/symbol or array)" if not [Array, String, Symbol].include?(key.class)
824
+
825
+ key = [key] if key.is_a?(Symbol) or key.is_a?(String)
826
+
827
+ key = key.flatten
828
+
829
+ oObject = rhGet(@hParams, key[0])
830
+ return false if oObject.nil?
831
+
832
+ if oObject.is_a?(ForjLib::Data)
833
+ # Return true if ObjectData Element is found when asked.
834
+ return true if key[1] == :ObjectData and oObject.type?(key[0]) == :object
835
+
836
+ # Return true if attritutes or controller object attributes found when asked.
837
+ return oObject.exist?(key[2..-1]) if key[1] == :attrs
838
+ return oObject.exist?(key[1..-1]) if key.length > 1
839
+ true
840
+ else
841
+ # By default true if found key hierarchy
842
+ (rhExist?(@hParams, key) == key.length)
843
+ end
844
+ end
845
+
846
+ #~ def get(*key)
847
+ #~ rhGet(@hParams, key)
848
+ #~ end
849
+
850
+ def type?(key)
851
+ return nil if rhExist?(@hParams, key) != 1
852
+ :data
853
+ :DataObject if @hParams[key].type?() == :object
854
+ end
855
+
856
+ def cObj(*key)
857
+ rhGet(@hParams, key, :object) if rhExist?(@hParams, key, :object) == 2
858
+ end
859
+
860
+ end
861
+
862
+ # Following class defines class levels function to
863
+ # declare framework objects.
864
+ # As each process needs to define new object to deal with
865
+ # require that process to define it with definition functions
866
+ # See definition.rb for functions to use.
867
+
868
+ class BaseDefinition
869
+ # Capitalized function are called to start a process. It is done by ForjObject.
870
+
871
+ # BaseCloud Object available functions.
872
+ def Create(sCloudObj)
873
+ return nil if not sCloudObj
874
+ raise ForjError.new(), "%s.Create: '%s' is not a known object type." % [self.class, sCloudObj] if rhExist?(@@meta_obj, sCloudObj) != 1
875
+
876
+ pProc = rhGet(@@meta_obj, sCloudObj, :lambdas, :create_e)
877
+
878
+ return nil if pProc.nil?
879
+
880
+ # Check required parameters
881
+ oObjMissing = _check_required(sCloudObj, :create_e, pProc).reverse
882
+
883
+ while oObjMissing.length >0
884
+ sElem = oObjMissing.pop
885
+
886
+ raise ForjError.new(),"Unable to create Object '%s'" % sElem if not Create(sElem)
887
+ oObjMissing = _check_required(sCloudObj, :create_e, pProc).reverse
888
+
889
+ raise ForjError.new(), "loop detection: '%s' is required but Create(%s) did not loaded it." % [sElem, sElem] if oObjMissing.include?(sElem)
890
+ end
891
+ @RuntimeContext[:oCurrentObj] = sCloudObj # Context: Default object used.
892
+
893
+ # build Function params to pass to the event handler.
894
+ aParams = _get_object_params(sCloudObj, :create_e, pProc,)
895
+ ForjLib.debug(2, "Create Object '%s' - Running '%s'" % [sCloudObj, pProc])
896
+
897
+ # Call the process function.
898
+ # At some point, the process will call the controller, via the framework.
899
+ # This controller call via the framework has the role to
900
+ # create an ObjectData well formatted, with _return_map function
901
+ # See Definition.connect/create/update/query/get functions (lowercase)
902
+ oObject = @oForjProcess.method(pProc).call(sCloudObj, aParams)
903
+ # return usually is the main object that the process called should provide.
904
+ # Save Object if the object has been created by the process, without controller
905
+ unless oObject.nil?
906
+ @ObjectData.add(oObject)
907
+ end
908
+ end
909
+
910
+ def Delete(sCloudObj)
911
+ return nil if not sCloudObj
912
+
913
+ raise ForjError.new(), "%s.Delete: '%s' is not a known object type." % [self.class, sCloudObj] if rhExist?(@@meta_obj, sCloudObj) != 1
914
+
915
+ pProc = rhGet(@@meta_obj, sCloudObj, :lambdas, :delete_e)
916
+
917
+ return nil if pProc.nil?
918
+
919
+ # Check required parameters
920
+ oObjMissing = _check_required(sCloudObj, :delete_e, pProc).reverse
921
+
922
+ while oObjMissing.length >0
923
+ sElem = oObjMissing.pop
924
+ raise ForjError.new(),"Unable to create Object '%s'" % sElem if not Create(sElem)
925
+ oObjMissing = _check_required(sCloudObj, :delete_e, pProc).reverse
926
+ raise ForjError.new(), "loop detection: '%s' is required but Delete(%s) did not loaded it." % [sElem, sElem] if oObjMissing.include?(sElem)
927
+ end
928
+ @RuntimeContext[:oCurrentObj] = sCloudObj # Context: Default object used.
929
+
930
+ # build Function params to pass to the event handler.
931
+ aParams = _get_object_params(sCloudObj, :delete_e, pProc)
932
+
933
+ bState = @oForjProcess.method(pProc).call(sCloudObj, aParams)
934
+ # return usually is the main object that the process called should provide.
935
+ if bState
936
+ @ObjectData.del(sCloudObj)
937
+ end
938
+
939
+ end
940
+
941
+ # This function returns a list of objects
942
+ def Query(sCloudObj, hQuery)
943
+
944
+ return nil if not sCloudObj
945
+
946
+ raise ForjError.new(), "$s.Get: '%s' is not a known object type." % [self.class, sCloudObj] if rhExist?(@@meta_obj, sCloudObj) != 1
947
+
948
+ # Check if we can re-use a previous query
949
+ oList = @ObjectData[:query, sCloudObj]
950
+ unless oList.nil?
951
+ if oList[:query] == hQuery
952
+ ForjLib.debug(3, "Using Object '%s' query cache : %s" % [sCloudObj, hQuery])
953
+ return oList
954
+ end
955
+ end
956
+
957
+ pProc = rhGet(@@meta_obj, sCloudObj, :lambdas, :query_e)
958
+
959
+ return nil if pProc.nil?
960
+
961
+ # Check required parameters
962
+ oObjMissing = _check_required(sCloudObj, :query_e, pProc).reverse
963
+
964
+ while oObjMissing.length >0
965
+ sElem = oObjMissing.pop
966
+ raise ForjError.new(),"Unable to create Object '%s'" % sElem if not Create(sElem)
967
+ oObjMissing = _check_required(sCloudObj, :query_e, pProc).reverse
968
+ raise ForjError.new(), "loop detection: '%s' is required but Query(%s) did not loaded it." % [sElem, sElem] if oObjMissing.include?(sElem)
969
+ end
970
+ @RuntimeContext[:oCurrentObj] = sCloudObj # Context: Default object used.
971
+
972
+ # build Function params to pass to the Process Event handler.
973
+ aParams = _get_object_params(sCloudObj, :query_e, pProc)
974
+
975
+ # Call the process function.
976
+ # At some point, the process will call the controller, via the framework.
977
+ # This controller call via the framework has the role to
978
+ # create an ObjectData well formatted, with _return_map function
979
+ # See Definition.connect/create/update/query/get functions (lowercase)
980
+ oObject = @oForjProcess.method(pProc).call(sCloudObj, hQuery, aParams)
981
+ # return usually is the main object that the process called should provide.
982
+ unless oObject.nil?
983
+ # Save Object if the object has been created by the process, without controller
984
+ @ObjectData.add(oObject)
985
+ end
986
+ end
987
+
988
+ def Get(sCloudObj, sUniqId)
989
+
990
+ return nil if not sCloudObj
991
+
992
+ raise ForjError.new(), "$s.Get: '%s' is not a known object type." % [self.class, sCloudObj] if rhExist?(@@meta_obj, sCloudObj) != 1
993
+
994
+ pProc = rhGet(@@meta_obj, sCloudObj, :lambdas, :get_e)
995
+
996
+ return nil if pProc.nil?
997
+
998
+ # Check required parameters
999
+ oObjMissing = _check_required(sCloudObj, :get_e, pProc).reverse
1000
+
1001
+ while oObjMissing.length >0
1002
+ sElem = oObjMissing.pop
1003
+ raise ForjError.new(),"Unable to create Object '%s'" % sElem if not Create(sElem)
1004
+ oObjMissing = _check_required(sCloudObj, :get_e, pProc).reverse
1005
+ raise ForjError.new(), "loop detection: '%s' is required but Get(%s) did not loaded it." % [sElem, sElem] if oObjMissing.include?(sElem)
1006
+ end
1007
+ @RuntimeContext[:oCurrentObj] = sCloudObj # Context: Default object used.
1008
+
1009
+ # build Function params to pass to the Process Event handler.
1010
+ aParams = _get_object_params(sCloudObj, :get_e, pProc)
1011
+
1012
+ # Call the process function.
1013
+ # At some point, the process will call the controller, via the framework.
1014
+ # This controller call via the framework has the role to
1015
+ # create an ObjectData well formatted, with _return_map function
1016
+ # See Definition.connect/create/update/query/get functions (lowercase)
1017
+ oObject = @oForjProcess.method(pProc).call(sCloudObj, sUniqId, aParams)
1018
+ # return usually is the main object that the process called should provide.
1019
+ unless oObject.nil?
1020
+ # Save Object if the object has been created by the process, without controller
1021
+ @ObjectData.add(oObject)
1022
+ end
1023
+ end
1024
+
1025
+ def Update(sCloudObj)
1026
+
1027
+ return nil if not sCloudObj
1028
+
1029
+ raise ForjError.new(), "$s.Update: '%s' is not a known object type." % [self.class, sCloudObj] if rhExist?(@@meta_obj, sCloudObj) != 1
1030
+
1031
+ pProc = rhGet(@@meta_obj, sCloudObj, :lambdas, :update_e)
1032
+
1033
+ return nil if pProc.nil?
1034
+
1035
+ # Check required parameters
1036
+ oObjMissing = _check_required(sCloudObj, :update_e, pProc).reverse
1037
+
1038
+ while oObjMissing.length >0
1039
+ sElem = oObjMissing.pop
1040
+ raise ForjError.new(),"Unable to create Object '%s'" % sElem if not Create(sElem)
1041
+ oObjMissing = _check_required(sCloudObj, :update_e, pProc).reverse
1042
+ raise ForjError.new(), "loop detection: '%s' is required but Update(%s) did not loaded it." % [sElem, sElem] if oObjMissing.include?(sElem)
1043
+ end
1044
+ @RuntimeContext[:oCurrentObj] = sCloudObj # Context: Default object used.
1045
+
1046
+ # build Function params to pass to the event handler.
1047
+ aParams = _get_object_params(sCloudObj, :update_e, pProc)
1048
+
1049
+ oObject = @oForjProcess.method(pProc).call(sCloudObj, aParams)
1050
+ # return usually is the main object that the process called should provide.
1051
+ unless oObject.nil?
1052
+ # Save Object if the object has been created by the process, without controller
1053
+ @ObjectData.add(oObject)
1054
+ end
1055
+ end
1056
+
1057
+ def Setup(sObjectType, sAccountName)
1058
+ # Loop in dependencies to get list of data object to setup
1059
+ raise ForjError,new(), "Setup: '%s' not a valid object type." if rhExist?(@@meta_obj, sObjectType) != 1
1060
+
1061
+ hAskStep = ForjDefault.get(:ask_step, :setup)
1062
+ aSetup = []
1063
+ hAskStep.each{ | value |
1064
+ aSetup << {
1065
+ :desc => value[:desc],
1066
+ :pre_step_handler => value[:pre_step_function],
1067
+ :order => [[]],
1068
+ :post_step_handler => value[:post_step_function]
1069
+ }
1070
+
1071
+ }
1072
+ oInspectedObjects = []
1073
+ oInspectObj = [sObjectType]
1074
+
1075
+ @oForjConfig.ac_load(sAccountName) if sAccountName
1076
+
1077
+ ForjLib.debug(2, "Setup is identifying account data to ask for '%s'" % sObjectType)
1078
+ while oInspectObj.length() >0
1079
+ # Identify data to ask
1080
+ # A data to ask is a data needs from an object type
1081
+ # which is declared in section of defaults.yaml
1082
+ # and is declared :account to true (in defaults.yaml or in process declaration - define_data)
1083
+
1084
+ sObjectType = oInspectObj.pop
1085
+ sAsk_step = 0
1086
+ ForjLib.debug(1, "Checking '%s'" % sObjectType)
1087
+ hTopParams = rhGet(@@meta_obj,sObjectType, :params)
1088
+ if hTopParams[:keys].nil?
1089
+ ForjLib.debug(1, "Warning! Object '%s' has no data/object needs. Check the process" % sObjectType)
1090
+ next
1091
+ end
1092
+ hTopParams[:keys].each { |sKeypath, hParams|
1093
+ oKeyPath = KeyPath.new(sKeypath)
1094
+ sKey = oKeyPath.sKey
1095
+ case hParams[:type]
1096
+ when :data
1097
+ hMeta = _get_meta_data(sKey)
1098
+ next if hMeta.nil?
1099
+ sAsk_step = hMeta[:ask_step] if rhExist?(hMeta, :ask_step) == 1 and hMeta[:ask_step].is_a?(Fixnum)
1100
+ ForjLib.debug(3, "#{sKey} is part of setup step #{sAsk_step}")
1101
+ aOrder = aSetup[sAsk_step][:order]
1102
+
1103
+ if oInspectedObjects.include?(sKey)
1104
+ ForjLib.debug(2, "#{sKey} is already asked. Ignored.")
1105
+ next
1106
+ end
1107
+ if hMeta[:account].is_a?(TrueClass)
1108
+ if not hMeta[:depends_on].is_a?(Array)
1109
+ Logging.warning("'%s' depends_on definition have to be an array." % oKeyPath.sFullPath) unless hMeta[:depends_on].nil?
1110
+ iLevel = 0
1111
+ bFound = true
1112
+ else
1113
+ # Searching highest level from dependencies.
1114
+ bFound = false
1115
+ iLevel = 0
1116
+ hMeta[:depends_on].each { |depend_key|
1117
+ aOrder.each_index { |iCurLevel|
1118
+ if aOrder[iCurLevel].include?(depend_key)
1119
+ bFound = true
1120
+ iLevel = [iLevel, iCurLevel + 1].max
1121
+ end
1122
+ }
1123
+ aOrder[iLevel] = [] if aOrder[iLevel].nil?
1124
+ }
1125
+ end
1126
+ if bFound
1127
+ if not aOrder[iLevel].include?(sKey)
1128
+ if hMeta[:ask_sort].is_a?(Fixnum)
1129
+ iOrder = hMeta[:ask_sort]
1130
+ if aOrder[iLevel][iOrder].nil?
1131
+ aOrder[iLevel][iOrder] = sKey
1132
+ else
1133
+ aOrder[iLevel].insert(iOrder, sKey)
1134
+ end
1135
+ ForjLib.debug(3, "S%s/L%s/O%s: '%s' added in setup list. " % [sAsk_step, iLevel, iOrder, sKey])
1136
+ else
1137
+ aOrder[iLevel] << sKey
1138
+ ForjLib.debug(3, "S%s/L%s/Last: '%s' added in setup list." % [sAsk_step, iLevel, sKey])
1139
+ end
1140
+ end
1141
+ end
1142
+ oInspectedObjects << sKey
1143
+ else
1144
+ ForjLib.debug(2, "#{sKey} used by #{sObjectType} won't be asked during setup. :account = true not set.")
1145
+ end
1146
+ when :CloudObject
1147
+ oInspectObj << sKey if not oInspectObj.include?(sKey) and not oInspectedObjects.include?(sKey)
1148
+ end
1149
+ }
1150
+ oInspectedObjects << sObjectType
1151
+ end
1152
+ ForjLib.debug(2, "Setup check if needs to add unrelated data in the process")
1153
+ hAskStep.each_index{ | iStep |
1154
+ value = hAskStep[iStep]
1155
+ if rhExist?(value, :add) == 1
1156
+ sKeysToAdd = rhGet(value, :add)
1157
+ sKeysToAdd.each { | sKeyToAdd |
1158
+ bFound = false
1159
+ aSetup[iStep][:order].each_index { | iOrder |
1160
+ sKeysToAsk = aSetup[iStep][:order][iOrder]
1161
+ unless sKeysToAsk.index(sKeyToAdd).nil?
1162
+ bFound = true
1163
+ break
1164
+ end
1165
+ }
1166
+ next if bFound
1167
+ iLevel = 0
1168
+ iOrder = aSetup[iStep][:order].length
1169
+ iAtStep = iStep
1170
+ hMeta = _get_meta_data(sKeyToAdd)
1171
+ if rhExist?(hMeta, :after) == 1
1172
+ sAfterKeys = hMeta[:after]
1173
+ sAfterKeys = [ sAfterKeys ] if not sAfterKeys.is_a?(Array)
1174
+ sAfterKeys.each{ |sAfterKey |
1175
+ bFound = false
1176
+ aSetup.each_index { |iStepToCheck|
1177
+ aSetup[iStepToCheck][:order].each_index { | iLevelToCheck |
1178
+ sKeysToAsk = aSetup[iStepToCheck][:order][iLevelToCheck]
1179
+ iOrderToCheck = sKeysToAsk.index(sAfterKey)
1180
+ unless iOrderToCheck.nil?
1181
+ iAtStep = iStepToCheck if iStepToCheck > iAtStep
1182
+ iLevel = iLevelToCheck if iLevelToCheck > iLevel
1183
+ iOrder = iOrderToCheck + 1 if iOrderToCheck + 1 > iOrder
1184
+ bFound = true
1185
+ break
1186
+ end
1187
+ }
1188
+ }
1189
+ }
1190
+ end
1191
+ aSetup[iAtStep][:order][iLevel].insert(iOrder, sKeyToAdd)
1192
+ ForjLib.debug(3, "S%s/L%s/O%s: '%s' added in setup list at position." % [iAtStep, iLevel, iOrder, sKeyToAdd])
1193
+ }
1194
+ end
1195
+ }
1196
+
1197
+ ForjLib.debug(2, "Setup will ask for :\n %s" % aSetup.to_yaml)
1198
+
1199
+ Logging.info("Configuring account : '#{config[:account_name]}', provider '#{config[:provider_name]}'")
1200
+
1201
+ # Ask for user input
1202
+ aSetup.each_index { | iStep |
1203
+ ForjLib.debug(2, "Ask step %s:" % iStep)
1204
+ puts "%s%s%s" % [ANSI.bold, aSetup[iStep][:desc], ANSI.clear] unless aSetup[iStep][:desc].nil?
1205
+ aOrder = aSetup[iStep][:order]
1206
+ aOrder.each_index { | iIndex |
1207
+ ForjLib.debug(2, "Ask order %s:" % iIndex)
1208
+ aOrder[iIndex].each { | sKey |
1209
+ hParam = _get_meta_data(sKey)
1210
+ hParam = {} if hParam.nil?
1211
+
1212
+ bOk = false
1213
+
1214
+ if hParam[:pre_step_function]
1215
+ pProc = hParam[:pre_step_function]
1216
+ bOk = not(@oForjProcess.method(pProc).call(sKey))
1217
+ end
1218
+
1219
+
1220
+ sDesc = "'%s' value" % sKey
1221
+ puts "#{sKey}: %s" % [hParam[:explanation]] unless rhGet(hParam, :explanation).nil?
1222
+ sDesc = hParam[:desc] unless hParam[:desc].nil?
1223
+ sDefault = @oForjConfig.get(sKey, hParam[:default_value])
1224
+ rValidate = nil
1225
+
1226
+ rValidate = hParam[:validate] unless hParam[:validate].nil?
1227
+ bRequired = (hParam[:required] == true)
1228
+ while not bOk
1229
+ bOk = true
1230
+ if not hParam[:list_values].nil?
1231
+ hValues = hParam[:list_values]
1232
+ sObjectToLoad = hValues[:object]
1233
+
1234
+ bListStrict = (hValues[:validate] == :list_strict)
1235
+
1236
+ case hValues[:query_type]
1237
+ when :controller_call
1238
+ oObject = @ObjectData[sObjectToLoad, :ObjectData]
1239
+ Logging.state("Loading #{sObjectToLoad}.")
1240
+ oObject = Create(sObjectToLoad) if oObject.nil?
1241
+ return nil if oObject.nil?
1242
+ oParams = ObjectData.new
1243
+ oParams.add(oObject)
1244
+ oParams << hValues[:query_params]
1245
+ raise ForjError.new(), "#{sKey}: query_type => :controller_call requires missing :query_call declaration (Controller function)" if hValues[:query_call].nil?
1246
+ pProc = hValues[:query_call]
1247
+ begin
1248
+ aList = @oProvider.method(pProc).call(sObjectToLoad, oParams)
1249
+ rescue => e
1250
+ raise ForjError.new(), "Error during call of '%s':\n%s" % [pProc, e.message]
1251
+ end
1252
+ when :query_call
1253
+ sQuery = {}
1254
+ sQuery = hValues[:query_params] unless hValues[:query_params].nil?
1255
+ Logging.state("Querying #{sObjectToLoad}.")
1256
+ oObjectList = Query(sObjectToLoad, sQuery)
1257
+ aList = []
1258
+ oObjectList.each { | oElem |
1259
+ aList << oElem[hValues[:value]]
1260
+ }
1261
+ aList.sort!
1262
+ when :process_call
1263
+ raise ForjError.new(), "#{sKey}: query_type => :process_call requires missing :query_call declaration (Provider function)" if hValues[:query_call].nil?
1264
+ pProc = hValues[:query_call]
1265
+ sObjectToLoad = hValues[:object]
1266
+ oParams = ObjectData.new
1267
+ oParams.add(oObject)
1268
+ oParams << hValues[:query_params]
1269
+ begin
1270
+ aList = @oForjProcess.method(pProc).call(sObjectToLoad, oParams)
1271
+ rescue => e
1272
+ raise ForjError.new(), "Error during call of '%s':\n%s" % [pProc, e.message]
1273
+ end
1274
+ else
1275
+ raise ForjError.new, "'%s' invalid. %s/list_values/values_type supports %s. " % [hValues[:values_type], sKey, [:provider_function]]
1276
+ end
1277
+ Logging.fatal(1, "%s requires a value from the '%s' query which is empty." % [sKey, sObjectToLoad])if aList.nil? and bListStrict
1278
+ aList = [] if aList.nil?
1279
+ if not bListStrict
1280
+ aList << "other"
1281
+ end
1282
+ say("Enter %s" % ((sDefault.nil?)? sDesc : sDesc + " |%s|" % sDefault))
1283
+ value = choose { | q |
1284
+ q.choices(*aList)
1285
+ q.default = sDefault if sDefault
1286
+ }
1287
+ if not bListStrict and value == "other"
1288
+ value = _ask(sDesc, sDefault, rValidate, hParam[:encrypted], bRequired)
1289
+ end
1290
+ else
1291
+ pValidateProc = hParam[:validate_function]
1292
+ pAskProc = hParam[:ask_function]
1293
+
1294
+ if pAskProc.nil?
1295
+ unless pValidateProc.nil?
1296
+ value = _ask(sDesc, sDefault, rValidate, hParam[:encrypted], bRequired)
1297
+ while not @oForjProcess.method(pValidateProc).call(value)
1298
+ value = _ask(sDesc, sDefault, rValidate, hParam[:encrypted], bRequired)
1299
+ end
1300
+ else
1301
+ value = _ask(sDesc, sDefault, rValidate, hParam[:encrypted], bRequired)
1302
+ end
1303
+ else
1304
+ unless pValidateProc.nil?
1305
+ value = @oForjProcess.method(pAskProc).call(sDesc, sDefault, rValidate, hParam[:encrypted], bRequired)
1306
+ while not @oForjProcess.method(pValidateProc).call(value)
1307
+ value = @oForjProcess.method(pAskProc).call(sDesc, sDefault, rValidate, hParam[:encrypted], bRequired)
1308
+ end
1309
+ else
1310
+ value = @oForjProcess.method(pAskProc).call(sDesc, sDefault, rValidate, hParam[:encrypted], bRequired)
1311
+ end
1312
+ end
1313
+ end
1314
+
1315
+ @oForjConfig.set(sKey, value)
1316
+ if hParam[:post_step_function]
1317
+ pProc = hParam[:post_step_function]
1318
+ bOk = @oForjProcess.method(pProc).call()
1319
+ end
1320
+ end
1321
+ }
1322
+ }
1323
+ }
1324
+ end
1325
+
1326
+ # Initialize Cloud object Data
1327
+
1328
+ def initialize(oForjConfig, oForjProcess, oForjProvider = nil)
1329
+ # Object Data object. Contains all loaded object data.
1330
+ # This object is used to build hParams as well.
1331
+ @ObjectData = ObjectData.new(true)
1332
+ #
1333
+ @RuntimeContext = {
1334
+ :oCurrentObj => nil
1335
+ }
1336
+
1337
+ @oForjConfig = oForjConfig
1338
+ raise ForjError.new(), "'%s' is not a valid ForjAccount or ForjConfig Object." % [oForjConfig.class] if not oForjConfig.is_a?(ForjAccount) and not oForjConfig.is_a?(ForjConfig)
1339
+
1340
+ @oProvider = oForjProvider
1341
+ if oForjProvider
1342
+ raise ForjError.new(), "'%s' is not a valid ForjProvider Object type." % [oForjProvider.class] if not oForjProvider.is_a?(BaseController)
1343
+ end
1344
+
1345
+ @oForjProcess = oForjProcess
1346
+ raise ForjError.new(), "'%s' is not a valid BaseProcess Object type." % [oForjProcess.class] if not oForjProcess.is_a?(BaseProcess)
1347
+
1348
+ @oForjProcess.set_BaseObject(self)
1349
+ end
1350
+
1351
+ # ------------------------------------------------------
1352
+ # Functions used by processes functions
1353
+ # ------------------------------------------------------
1354
+ # Ex: object.set_data(...)
1355
+ # config
1356
+
1357
+
1358
+ # Function to manipulate the config object.
1359
+ # 2 kind of functions:
1360
+ # - set (key, value) and []=(key, value)
1361
+ # From processes, you can set a runtime data with:
1362
+ # config.set(key, value)
1363
+ # OR
1364
+ # config[key] = value
1365
+ #
1366
+ # - get (key, default) and [](key, default)
1367
+ # default is an optional value.
1368
+ # From processes, you can get a data (runtime/account/config.yaml or defaults.yaml) with:
1369
+ # config.get(key)
1370
+ # OR
1371
+ # config[key]
1372
+
1373
+ def config
1374
+ raise ForjError.new(), "No config object loaded." if not @oForjConfig
1375
+ @oForjConfig
1376
+ end
1377
+
1378
+ def format_query(sObjectType, oControlerObject, hQuery)
1379
+ {
1380
+ :object => oControlerObject,
1381
+ :object_type => :object_list,
1382
+ :list_type => sObjectType,
1383
+ :list => [],
1384
+ :query => hQuery
1385
+ }
1386
+ end
1387
+
1388
+ def format_object(sCloudObj, oMiscObject)
1389
+ return nil if not sCloudObj or not [String, Symbol].include?(sCloudObj.class)
1390
+
1391
+ sCloudObj = sCloudObj.to_sym if sCloudObj.class == String
1392
+
1393
+ oCoreObject = {
1394
+ :object_type => sCloudObj,
1395
+ :attrs => {},
1396
+ :object => oMiscObject,
1397
+ }
1398
+ end
1399
+
1400
+ def get_data_metadata(sKey)
1401
+ _get_meta_data(sKey)
1402
+ end
1403
+
1404
+ # Before doing a query, mapping fields
1405
+ # Transform Object query field to Provider query Fields
1406
+ def query_map(sCloudObj, hParams)
1407
+ return nil if not sCloudObj or not [String, Symbol].include?(sCloudObj.class)
1408
+ return {} if not hParams
1409
+
1410
+ sCloudObj = sCloudObj.to_sym if sCloudObj.class == String
1411
+
1412
+ hReturn = {}
1413
+ hMap = rhGet(@@meta_obj, sCloudObj, :query_mapping)
1414
+ hParams.each { |key, value|
1415
+ oKeyPath = KeyPath.new(key)
1416
+ sKeyPath = oKeyPath.sFullPath
1417
+ raise ForjError.new(), "Forj query field '%s.%s' not defined by class '%s'" % [sCloudObj, oKeyPath.sKey, self.class] if not hMap.key?(oKeyPath.sFullPath)
1418
+ oMapPath = KeyPath.new(hMap[oKeyPath.sFullPath])
1419
+ hValueMapping = rhGet(@@meta_obj, sCloudObj, :value_mapping, sKeyPath)
1420
+ if hValueMapping
1421
+ raise ForjError.new(), "'%s.%s': No value mapping for '%s'" % [sCloudObj, oKeyPath.sKey, value] if rhExist?(hValueMapping, value) != 1
1422
+
1423
+ rhSet(hReturn, hValueMapping[value], oMapPath.aTree)
1424
+ else
1425
+ rhSet(hReturn, value, oMapPath.aTree)
1426
+ end
1427
+ }
1428
+ hReturn
1429
+ end
1430
+
1431
+ # Used by the Process.
1432
+ # Ask controller get_attr to get a data
1433
+ # The result is the data of a defined data attribute.
1434
+ # If the value is normally mapped (value mapped), the value is
1435
+ # returned as a recognized data attribute value.
1436
+ def get_attr(oObject, key)
1437
+
1438
+ raise ForjError.new(), "'%s' is not a valid Object type. " % [oObject.class] if not oObject.is_a?(Hash) and rhExist?(oObject, :object_type) != 1
1439
+ sCloudObj = oObject[:object_type]
1440
+ oKeyPath = KeyPath.new(key)
1441
+ raise ForjError.new(), "'%s' key is not declared as data of '%s' CloudObject. You may need to add obj_needs..." % [oKeyPath.sKey, sCloudObj] if rhExist?(@@meta_obj, sCloudObj, :returns, oKeyPath.sFullPath) != 3
1442
+ begin
1443
+ oMapPath = KeyPath.new(rhGet(@@meta_obj, sCloudObj, :returns, oKeyPath.sFullPath))
1444
+ hMap = oMapPath.sFullPath
1445
+ value = @oProvider.get_attr(get_cObject(oObject), hMap)
1446
+
1447
+ hValueMapping = rhGet(@@meta_obj, sCloudObj, :value_mapping, oKeyPath.sFullPath)
1448
+
1449
+ if hValueMapping
1450
+ hValueMapping.each { | found_key, found_value |
1451
+ if found_value == value
1452
+ value = found_key
1453
+ break
1454
+ end
1455
+ }
1456
+ end
1457
+ rescue => e
1458
+ raise ForjError.new(), "'%s.get_attr' fails to provide value of '%s'" % [oProvider.class, key]
1459
+ end
1460
+ end
1461
+
1462
+ # Register the object to the internal @ObjectData instance
1463
+ def register(oObject, sObjectType = nil, sDataType = :object)
1464
+ if oObject.is_a?(ForjLib::Data)
1465
+ oDataObject = oObject
1466
+ else
1467
+ raise ForjError.new(), "Unable to register an object '%s' as ForjLib::Data object if ObjectType is not given." % [ oObject.class ] if not sObjectType
1468
+ oDataObject = ForjLib::Data.new(sDataType)
1469
+ oDataObject.set(oObject, sObjectType) { | sObjType, oControlerObject |
1470
+ _return_map(sObjType, oControlerObject)
1471
+ }
1472
+ end
1473
+ @ObjectData.add oDataObject
1474
+ end
1475
+
1476
+ # get an attribute/object/... from an object.
1477
+ def get_data(oObj, *key)
1478
+ if oObj.is_a?(Hash) and oObj.key?(:object_type)
1479
+ oObjData = ObjectData.new
1480
+ oObjData << oObj
1481
+ else
1482
+ oObjData = @ObjectData
1483
+ end
1484
+ oObjData[oObj, *key]
1485
+ end
1486
+
1487
+ #~ def hParams(sCloudObj, hParams)
1488
+ #~ aParams = _get_object_params(sCloudObj, ":ObjectData.hParams")
1489
+ #~ end
1490
+
1491
+ def get_cObject(oObject)
1492
+ return nil if rhExist?(oObject, :object) != 1
1493
+ rhGet(oObject, :object)
1494
+ end
1495
+
1496
+ # a Process can execute any kind of predefined controler task.
1497
+ # Those function build hParams with Provider compliant data (mapped)
1498
+ # Results are formatted as usual framework Data object and stored.
1499
+ def connect(sObjectType)
1500
+
1501
+ hParams = _get_object_params(sObjectType, :create_e, :connect, true)
1502
+ oControlerObject = @oProvider.connect(sObjectType, hParams)
1503
+ oDataObject = ForjLib::Data.new
1504
+ oDataObject.set(oControlerObject, sObjectType) { | sObjType, oObject |
1505
+ begin
1506
+ _return_map(sObjType, oObject)
1507
+ rescue => e
1508
+ raise ForjError.new(), "connect %s.%s : %s" % [@oForjProcess.class, sObjectType, e.message]
1509
+ end
1510
+ }
1511
+ @ObjectData.add oDataObject
1512
+ oDataObject
1513
+ end
1514
+
1515
+ def create(sObjectType)
1516
+ # The process ask the controller to create the object.
1517
+ # hParams have to be fully readable by the controller.
1518
+ hParams = _get_object_params(sObjectType, :create_e, :create, true)
1519
+ oControlerObject = @oProvider.create(sObjectType, hParams)
1520
+ oDataObject = ForjLib::Data.new
1521
+ oDataObject.set(oControlerObject, sObjectType) { | sObjType, oObject |
1522
+ begin
1523
+ _return_map(sObjType, oObject)
1524
+ rescue => e
1525
+ raise ForjError.new(), "create %s.%s : %s" % [@oForjProcess.class, sObjectType, e.message]
1526
+ end
1527
+ }
1528
+ @ObjectData.add oDataObject
1529
+
1530
+ oDataObject
1531
+ end
1532
+
1533
+ # The controller must return true to inform about the real deletion
1534
+ def delete(sObjectType)
1535
+ hParams = _get_object_params(sObjectType, :delete_e, :delete, true)
1536
+ bState = @oProvider.delete(sObjectType, hParams)
1537
+ @ObjectData.delete(sCloudObj) if bState
1538
+ bState
1539
+ end
1540
+
1541
+ def get(sObjectType, sUniqId)
1542
+
1543
+ hParams = _get_object_params(sObjectType, :get_e, :get, true)
1544
+
1545
+ oControlerObject = @oProvider.get(sObjectType, sUniqId, hParams)
1546
+ oDataObject = ForjLib::Data.new
1547
+ oDataObject.set(oControlerObject, sObjectType) { | sObjType, oObject |
1548
+ begin
1549
+ _return_map(sObjType, oObject)
1550
+ rescue => e
1551
+ raise ForjError.new(), "get %s.%s : %s" % [@oForjProcess.class, sObjectType, e.message]
1552
+ end
1553
+ }
1554
+ @ObjectData.add oDataObject
1555
+
1556
+ oDataObject
1557
+ end
1558
+
1559
+ def query(sObjectType, hQuery)
1560
+
1561
+ # Check if we can re-use a previous query
1562
+ oList = @ObjectData[:query, sObjectType]
1563
+ unless oList.nil?
1564
+ if oList[:query] == hQuery
1565
+ ForjLib.debug(3, "Using Object '%s' query cache : %s" % [sObjectType, hQuery])
1566
+ return oList
1567
+ end
1568
+ end
1569
+
1570
+
1571
+ hParams = _get_object_params(sObjectType, :query_e, :query, true)
1572
+ sProviderQuery = query_map(sObjectType, hQuery)
1573
+
1574
+ oControlerObject = @oProvider.query(sObjectType, sProviderQuery, hParams)
1575
+
1576
+ oDataObjects = ForjLib::Data.new :list
1577
+ oDataObjects.set(oControlerObject, sObjectType, hQuery) { | sObjType, key |
1578
+ begin
1579
+ _return_map(sObjType, key)
1580
+ rescue => e
1581
+ raise ForjError.new(), "query %s.%s : %s" % [@oForjProcess.class, sObjectType, e.message]
1582
+ end
1583
+ }
1584
+
1585
+ ForjLib.debug(2, "Object %s - queried. Found %s object(s)." % [sObjectType, oDataObjects.length()])
1586
+
1587
+ @ObjectData.add oDataObjects
1588
+ oDataObjects
1589
+ end
1590
+
1591
+ def update(sObjectType)
1592
+ # Need to detect data updated and update the Controler object with the controler
1593
+
1594
+ hParams = _get_object_params(sObjectType, :update_e, :update, true)
1595
+
1596
+ oObject = hParams.get(sObjectType)
1597
+ oControlerObject = hParams[sObjectType]
1598
+
1599
+ bUpdated = false
1600
+ oObject[attrs].each { |key, value |
1601
+ # hValueMapping = rhGet(@@meta_obj, sCloudObj, :value_mapping, key)
1602
+ oKeyPath = KeyPath.new(key)
1603
+ oMapPath = KeyPath.new(rhGet(@@meta_obj, sCloudObj, :returns, oKeyPath.sFullPath))
1604
+ old_value = @oProvider.get_attr(oControlerObject, oMapPath.aTree)
1605
+ if value != old_value
1606
+ bUpdated = true
1607
+ @oProvider.set_attr(oControlerObject, hMap, value)
1608
+ ForjLib.debug(2, "%s.%s - Updating: %s = %s (old : %s)" % [@oForjProcess.class, sObjectType, key, value, old_value])
1609
+ end
1610
+ }
1611
+ oControlerObject = @oProvider.update(sObjectType, hParams) if bUpdated
1612
+ ForjLib.debug(1, "%s.%s - Saved." % [@oForjProcess.class, sObjectType])
1613
+ oDataObject = ForjLib::Data.new
1614
+ oDataObject.set(oControlerObject, sObjectType) { | sObjType, oObject |
1615
+ begin
1616
+ _return_map(sObjType, oObject)
1617
+ rescue => e
1618
+ raise ForjError.new(), "update %s.%s : %s" % [@oForjProcess.class, sObjectType, e.message]
1619
+ end
1620
+ }
1621
+ @ObjectData.add oDataObject
1622
+
1623
+ oDataObject
1624
+ end
1625
+
1626
+
1627
+ private
1628
+
1629
+ # -------------------------------------------------------------------------
1630
+ # Functions available for Process to communicate with the controler Object
1631
+ # -------------------------------------------------------------------------
1632
+ def cloud_obj_requires(sCloudObj, res = {})
1633
+ aCaller = caller
1634
+ aCaller.pop
1635
+
1636
+ return res if @ObjectData.exist?(sCloudObj)
1637
+ #~ return res if rhExist?(@CloudData, sCloudObj) == 1
1638
+
1639
+ rhGet(@@meta_obj,sCloudObj, :params).each { |key, hParams|
1640
+ case hParams[:type]
1641
+ when :data
1642
+ if hParams.key?(:array)
1643
+ hParams[:array].each{ | aElem |
1644
+ aElem = aElem.clone
1645
+ aElem.pop # Do not go until last level, as used to loop next.
1646
+ rhGet(hParams, aElem).each { | subkey, hSubParam |
1647
+ next if aElem.length == 0 and [:array, :type].include?(subkey)
1648
+ if hSubParams[:required] and @oForjConfig.get(subkey).nil?
1649
+ res[subkey] = hSubParams
1650
+ end
1651
+ }
1652
+ }
1653
+ else
1654
+ if hParams[:required] and @oForjConfig.get(key).nil?
1655
+ res[key] = hParams
1656
+ end
1657
+ end
1658
+ when :CloudObject
1659
+ #~ if hParams[:required] and rhExist?(@CloudData, sCloudObj) != 1
1660
+ if hParams[:required] and not @ObjectData.exist?(sCloudObj)
1661
+ res[key] = hParams
1662
+ cloud_obj_requires(key, res)
1663
+ end
1664
+ end
1665
+ }
1666
+ res
1667
+ end
1668
+
1669
+ def get_object(sCloudObj)
1670
+ #~ return nil if rhExist?(@CloudData, sCloudObj) != 1
1671
+ return nil if not @ObjectData.exist?(sCloudObj)
1672
+ @ObjectData[sCloudObj, :ObjectData]
1673
+ #~ rhGet(@CloudData, sCloudObj)
1674
+ end
1675
+
1676
+ def objectExist?(sCloudObj)
1677
+ @ObjectData.exist?(sCloudObj)
1678
+ #~ (rhExist?(@CloudData, sCloudObj) != 1)
1679
+ end
1680
+
1681
+ def get_forjKey(oCloudData, key)
1682
+ return nil if not @ObjectData.exist?(sCloudObj)
1683
+ @ObjectData[sCloudObj, :attrs, key]
1684
+ #~ return nil if rhExist?(oCloudData, sCloudObj) != 1
1685
+ #~ rhGet(oCloudData, sCloudObj, :attrs, key)
1686
+ end
1687
+ end