forj 0.0.48 → 1.0.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.
@@ -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