├── .DS_Store ├── images ├── screenshot01.jpg ├── screenshot02.jpg ├── screenshot03.jpg ├── screenshot04.jpg ├── screenshot05.jpg ├── screenshot06.jpg ├── screenshot07.jpg ├── screenshot08.jpg ├── screenshot09.jpg └── screenshot10.jpg ├── investigative_debugging.zip ├── object_walker_reader-1.8.tar.gz ├── object_walker_reader-1.9.tar.gz ├── object_walker_reader-2.0.tar.gz ├── object_walker_reader-1.8-1.noarch.rpm ├── object_walker_reader-1.9-1.noarch.rpm ├── object_walker_reader-2.0-1.noarch.rpm ├── Investigative_Debugging ├── System │ ├── __namespace__.yaml │ └── Request.class │ │ ├── object_walker.yaml │ │ └── __class__.yaml ├── Discovery │ ├── __namespace__.yaml │ └── ObjectWalker.class │ │ ├── object_walker.yaml │ │ ├── __methods__ │ │ ├── object_walker.yaml │ │ └── object_walker.rb │ │ ├── configuration.yaml │ │ └── __class__.yaml ├── __domain__.yaml └── .manifest.yaml ├── object_walker_reader.spec ├── CHANGELOG.md ├── sample_whitelists ├── object_walker_reader.rb ├── README.md ├── object_walker.rb └── object_associations /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/.DS_Store -------------------------------------------------------------------------------- /images/screenshot01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot01.jpg -------------------------------------------------------------------------------- /images/screenshot02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot02.jpg -------------------------------------------------------------------------------- /images/screenshot03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot03.jpg -------------------------------------------------------------------------------- /images/screenshot04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot04.jpg -------------------------------------------------------------------------------- /images/screenshot05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot05.jpg -------------------------------------------------------------------------------- /images/screenshot06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot06.jpg -------------------------------------------------------------------------------- /images/screenshot07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot07.jpg -------------------------------------------------------------------------------- /images/screenshot08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot08.jpg -------------------------------------------------------------------------------- /images/screenshot09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot09.jpg -------------------------------------------------------------------------------- /images/screenshot10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/images/screenshot10.jpg -------------------------------------------------------------------------------- /investigative_debugging.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/investigative_debugging.zip -------------------------------------------------------------------------------- /object_walker_reader-1.8.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/object_walker_reader-1.8.tar.gz -------------------------------------------------------------------------------- /object_walker_reader-1.9.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/object_walker_reader-1.9.tar.gz -------------------------------------------------------------------------------- /object_walker_reader-2.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/object_walker_reader-2.0.tar.gz -------------------------------------------------------------------------------- /object_walker_reader-1.8-1.noarch.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/object_walker_reader-1.8-1.noarch.rpm -------------------------------------------------------------------------------- /object_walker_reader-1.9-1.noarch.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/object_walker_reader-1.9-1.noarch.rpm -------------------------------------------------------------------------------- /object_walker_reader-2.0-1.noarch.rpm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pemcg/object_walker/HEAD/object_walker_reader-2.0-1.noarch.rpm -------------------------------------------------------------------------------- /Investigative_Debugging/System/__namespace__.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: namespace 3 | version: 1.0 4 | object: 5 | attributes: 6 | name: System 7 | description: 8 | display_name: 9 | priority: 10 | enabled: 11 | -------------------------------------------------------------------------------- /Investigative_Debugging/Discovery/__namespace__.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: namespace 3 | version: 1.0 4 | object: 5 | attributes: 6 | name: Discovery 7 | description: 8 | display_name: 9 | priority: 10 | enabled: 11 | -------------------------------------------------------------------------------- /Investigative_Debugging/Discovery/ObjectWalker.class/object_walker.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: instance 3 | version: 1.0 4 | object: 5 | attributes: 6 | display_name: 7 | name: object_walker 8 | inherits: 9 | description: 10 | fields: 11 | - execute: 12 | value: object_walker 13 | -------------------------------------------------------------------------------- /Investigative_Debugging/__domain__.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: domain 3 | version: 1.0 4 | object: 5 | attributes: 6 | name: Investigative_Debugging 7 | description: 8 | display_name: 9 | priority: 4 10 | enabled: true 11 | tenant_id: 1 12 | source: user 13 | top_level_namespace: 14 | -------------------------------------------------------------------------------- /Investigative_Debugging/System/Request.class/object_walker.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: instance 3 | version: 1.0 4 | object: 5 | attributes: 6 | display_name: 7 | name: object_walker 8 | inherits: 9 | description: 10 | fields: 11 | - rel1: 12 | value: "/Discovery/ObjectWalker/object_walker" 13 | -------------------------------------------------------------------------------- /Investigative_Debugging/Discovery/ObjectWalker.class/__methods__/object_walker.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: method 3 | version: 1.0 4 | object: 5 | attributes: 6 | name: object_walker 7 | display_name: 8 | description: 9 | scope: instance 10 | language: ruby 11 | location: inline 12 | options: {} 13 | inputs: [] 14 | -------------------------------------------------------------------------------- /object_walker_reader.spec: -------------------------------------------------------------------------------- 1 | %define name object_walker_reader 2 | %define release 1 3 | %define version 2.0 4 | Name: %{name} 5 | Version: %{version} 6 | Release: %{release} 7 | Summary: Extract the object_walker output from automation.log 8 | 9 | License: MIT 10 | URL: https://github.com/pemcg/object_walker 11 | Source0: %{name}-%{version}.tar.gz 12 | BuildRoot: %{_tmppath}/%{name} 13 | BuildArch: noarch 14 | 15 | %description 16 | object_walker_reader extracts the output from object_walker and writes it out in a nicely formatted indented manner 17 | 18 | %prep 19 | %setup -q -c -n %{name} 20 | 21 | %build 22 | 23 | %install 24 | rm -rf %{buildroot} 25 | mkdir -p %{buildroot}/root 26 | install -m 0755 %{name}.rb %{buildroot}/root/%{name}.rb 27 | 28 | %files 29 | 30 | %defattr(-,root,root) 31 | %attr(0755,root,root) /root/object_walker_reader.rb 32 | 33 | %doc 34 | 35 | %changelog 36 | 37 | -------------------------------------------------------------------------------- /Investigative_Debugging/Discovery/ObjectWalker.class/configuration.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: instance 3 | version: 1.0 4 | object: 5 | attributes: 6 | display_name: 7 | name: configuration 8 | inherits: 9 | description: 10 | fields: 11 | - print_evm_object: 12 | value: 'false' 13 | - print_evm_parent: 14 | value: 'false' 15 | - print_nil_values: 16 | value: 'true' 17 | - walk_association_policy: 18 | value: whitelist 19 | - walk_association_whitelist: 20 | value: "{'MiqAeServiceServiceTemplateProvisionTask':['source','destination','miq_request','miq_request_task','miq_request_tasks','service_resource'], 21 | 'MiqAeServiceServiceTemplateProvisionRequest':['miq_request','miq_request_tasks','requester','resource','source'], 22 | 'MiqAeServiceServiceTemplate':['service_resources'], 'MiqAeServiceServiceResource':['resource','service_template'], 23 | 'MiqAeServiceMiqProvisionRequest':['miq_request','miq_request_tasks','miq_provisions','requester','resource','source','vm_template'], 24 | 'MiqAeServiceMiqProvisionRequestTemplate':['miq_request','miq_request_tasks'], 25 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Provision':['source','destination','miq_provision_request','miq_request','miq_request_task','vm','vm_template'], 26 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Provision':['ALL'], 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_Vm':['availability_zone','cloud_network','cloud_subnet','cloud_subnets'], 27 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Vm':['ems_cluster','ems_folder','resource_pool','ext_management_system','storage','service','hardware','operating_system'], 28 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm':['ems_cluster','resource_pool','ext_management_system','storage','service','hardware'], 29 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_Vm':['availability_zone','cloud_networks','cloud_subnets'], 30 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_Vm':['flavor','floating_ip'], 31 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Vm':['miq_provision','network_ports','network_routers','operating_system'], 32 | 'MiqAeServiceHardware':['nics','guest_devices','ports','vm'], 'MiqAeServiceUser':['current_group','current_tenant'], 33 | 'MiqAeServiceGuestDevice':['hardware','lan','network'], 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Template':['operating_system'], 34 | 'MiqAeServiceManageIQ_Providers_AnsibleTower_ConfigurationManager_ConfigurationScript':['ALL'], 35 | 'MiqAeServiceManageIQ_Providers_AnsibleTower_ConfigurationManager_Job':['ALL']}" 36 | -------------------------------------------------------------------------------- /Investigative_Debugging/Discovery/ObjectWalker.class/__class__.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: class 3 | version: 1.0 4 | object: 5 | attributes: 6 | description: 7 | display_name: 8 | name: ObjectWalker 9 | type: 10 | inherits: 11 | visibility: 12 | owner: 13 | schema: 14 | - field: 15 | aetype: attribute 16 | name: print_evm_object 17 | display_name: 18 | datatype: boolean 19 | priority: 1 20 | owner: 21 | default_value: 22 | substitute: true 23 | message: create 24 | visibility: 25 | collect: 26 | scope: 27 | description: 28 | condition: 29 | on_entry: 30 | on_exit: 31 | on_error: 32 | max_retries: 33 | max_time: 34 | - field: 35 | aetype: attribute 36 | name: print_evm_parent 37 | display_name: 38 | datatype: boolean 39 | priority: 2 40 | owner: 41 | default_value: 42 | substitute: true 43 | message: create 44 | visibility: 45 | collect: 46 | scope: 47 | description: 48 | condition: 49 | on_entry: 50 | on_exit: 51 | on_error: 52 | max_retries: 53 | max_time: 54 | - field: 55 | aetype: attribute 56 | name: print_nil_values 57 | display_name: 58 | datatype: boolean 59 | priority: 3 60 | owner: 61 | default_value: 62 | substitute: true 63 | message: create 64 | visibility: 65 | collect: 66 | scope: 67 | description: 68 | condition: 69 | on_entry: 70 | on_exit: 71 | on_error: 72 | max_retries: 73 | max_time: 74 | - field: 75 | aetype: attribute 76 | name: walk_association_policy 77 | display_name: 78 | datatype: string 79 | priority: 4 80 | owner: 81 | default_value: 82 | substitute: true 83 | message: create 84 | visibility: 85 | collect: 86 | scope: 87 | description: 88 | condition: 89 | on_entry: 90 | on_exit: 91 | on_error: 92 | max_retries: 93 | max_time: 94 | - field: 95 | aetype: attribute 96 | name: walk_association_whitelist 97 | display_name: 98 | datatype: string 99 | priority: 5 100 | owner: 101 | default_value: 102 | substitute: true 103 | message: create 104 | visibility: 105 | collect: 106 | scope: 107 | description: 108 | condition: 109 | on_entry: 110 | on_exit: 111 | on_error: 112 | max_retries: 113 | max_time: 114 | - field: 115 | aetype: attribute 116 | name: walk_association_blacklist 117 | display_name: 118 | datatype: string 119 | priority: 6 120 | owner: 121 | default_value: 122 | substitute: true 123 | message: create 124 | visibility: 125 | collect: 126 | scope: 127 | description: 128 | condition: 129 | on_entry: 130 | on_exit: 131 | on_error: 132 | max_retries: 133 | max_time: 134 | - field: 135 | aetype: method 136 | name: execute 137 | display_name: 138 | datatype: string 139 | priority: 7 140 | owner: 141 | default_value: 142 | substitute: true 143 | message: create 144 | visibility: 145 | collect: 146 | scope: 147 | description: 148 | condition: 149 | on_entry: 150 | on_exit: 151 | on_error: 152 | max_retries: 153 | max_time: 154 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## 2.0 (22-Aug-2018) 4 | Re-factored to be callable as an embedded method in CFME 5.9/ManageIQ Gaprindashvili. Configuration parameters 5 | are now in a new instance called 'configuration' 6 | 7 | ## 1.10 (21-Oct-2017) 8 | More intelligent handing of broken associations. Includes a corresponding update to object_walker_reader.rb 9 | 10 | ## 1.9.3 (25-Jan-2017) 11 | Bugfix - attributes containing arrays of service models weren't displayed 12 | 13 | ## 1.9.2 (22-Nov-2016) 14 | Changed default value of $print\_evm\_parent to false 15 | 16 | ## 1.9.1 (08-Nov-2016) 17 | Bugfix to work with ManageIQ Euwe/CFME 5.7 18 | 19 | ## 1.9 (14-Oct-2016) 20 | Various bugfixes. Only call print\_tags if ServiceModelBase supports the taggable? method (CFME 5.6.2/Darga-4 and later). 21 | Changed instance variables to be global variables (what was I thinking originally?) 22 | 23 | ## 1.8 (21-Jul-2016) 24 | Added support for reading variables from the model, and took the black/whitelists out of the in-line code. Added print\_tags and print\_custom\_attributes. 25 | 26 | ## 1.7-1 (25-Apr-2016) 27 | Added more of the base methods to the methods listing of objects 28 | 29 | ## 1.7 (08-Dec-2015) 30 | Re-work indentation. We now indicate indentation level as a numeric value, and let the reader insert the actual indent space string. 31 | Also add some of the new CFME 5.5 format objects to the whitelist 32 | 33 | ## 1.6-2 (30-Oct-2015) 34 | Re-wrote walk\_object\_hierarchy to include walk\_object\_hierarchy. Now walks (correctly) the entire structure. 35 | 36 | ## 1.6-1 (19-Oct-2015) 37 | Reformatted the walk\_association white/blacklists for appearance 38 | 39 | ## 1.6 (06-Oct-2015) 40 | Refactored several internal methods. Add unique ID to dump output to allow interleaved dumps to be detected. 41 | Added object hierarchy dump. 42 | Changed output format slightly. 43 | Allow for an override of @walk\_association\_whitelist to be input via a dialog element named 'dialog\_walk\_association\_whitelist' 44 | 45 | ## 1.5-3 (12-Jul-2015) 46 | Refactored print\_attributes slightly to allow for the fact that options hash keys can be strings or symbols (a mix of the two causes sort to error) 47 | 48 | ## 1.5-2 (16-Apr-2015) 49 | Dump $evm.object rather than $evm.current - they are the same but more code examples use 50 | $evm.object so it's less ambiguous and possibly more useful to dump this 51 | 52 | ## 1.5-1 (16-Apr-2015) 53 | Fixed a bug where sometimes the return from calling object.attributes isn't iterable 54 | 55 | ## 1.5 (15-Apr-2015) 56 | Correctly format attributes that are actually hash keys (object['attribute'] rather than 57 | object.attribute). This includes most of the attributes of $evm.root which had previously 58 | been displayed incorrectly 59 | 60 | ## 1.4-7 (14-Apr-2015) 61 | Don't try to dump $evm.parent if it's a NilClass (e.g. if vmdb\_object\_type = automation\_task) 62 | 63 | ## 1.4-6 (14-Apr-2015) 64 | Dump $evm.current.attributes if there are any (arguments passed from a $evm.instantiate call) 65 | e.g. $evm.instantiate("Discovery/Methods/ObjectWalker?provider=#{provider}&lunch=sandwich") 66 | 67 | ## 1.4-5 (29-Mar-2015) 68 | Only dump the associations, methods and virtual columns of an MiqAeMethodService::* class 69 | 70 | ## 1.4-4 (08-Mar-2015) 71 | Walk $evm.parent after $evm.root 72 | 73 | ## 1.4-3 (02-Mar-2015) 74 | Detect duplicate entries in the associations list for each object 75 | 76 | ## 1.4-2 (27-Feb-2015) 77 | Dump some $evm attributes first, and print the URI for an object type of DRb::DRbObject 78 | 79 | ## 1.4-1 (19-Feb-2015) 80 | Changed singular/plural detection code in walk\_association to use active\_support/core\_ext/string 81 | 82 | ## 1.4 (15-Feb-2015) 83 | Added print\_methods, renamed to object\_walker 84 | 85 | ## 1.3 (25-Sep-2014) 86 | Debugged exception handling, changed some output strings 87 | 88 | ## 1.2 (24-Sep-2014) 89 | Changed exception handling logic slightly 90 | 91 | ## 1.1 (22-Sep-2014) 92 | Added blacklisting/whitelisting to the walk\_association functionality 93 | 94 | ## 1.0 (18-Sep-2014) 95 | First release -------------------------------------------------------------------------------- /Investigative_Debugging/.manifest.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | __domain__.yaml: 3 | classes: 2 4 | instances: 3 5 | method_files: 1 6 | method_instances: 1 7 | created_on: !ruby/object:ActiveSupport::TimeWithZone 8 | utc: &1 2018-08-22 12:26:43.968531000 Z 9 | zone: &2 !ruby/object:ActiveSupport::TimeZone 10 | name: Etc/UTC 11 | time: *1 12 | updated_on: !ruby/object:ActiveSupport::TimeWithZone 13 | utc: &3 2018-08-22 14:55:45.569561000 Z 14 | zone: *2 15 | time: *3 16 | size: 224 17 | sha1: rNhmIi+M8NR/7YRAilHUSWildvE= 18 | Investigative_Debugging/System/__namespace__.yaml: 19 | created_on: !ruby/object:ActiveSupport::TimeWithZone 20 | utc: &4 2018-08-22 12:26:44.719485000 Z 21 | zone: *2 22 | time: *4 23 | updated_on: !ruby/object:ActiveSupport::TimeWithZone 24 | utc: &5 2018-08-22 12:26:44.719485000 Z 25 | zone: *2 26 | time: *5 27 | size: 145 28 | sha1: rs4yeLg3wssLEk0drD1PflXqfCo= 29 | Investigative_Debugging/System/Request.class/__class__.yaml: 30 | created_on: !ruby/object:ActiveSupport::TimeWithZone 31 | utc: &6 2018-08-22 12:26:44.900652000 Z 32 | zone: *2 33 | time: *6 34 | updated_on: !ruby/object:ActiveSupport::TimeWithZone 35 | utc: &7 2018-08-22 12:26:44.911603000 Z 36 | zone: *2 37 | time: *7 38 | size: 7481 39 | sha1: UeYRmK6LXqIVwZlOXlv2h1sBjGE= 40 | Investigative_Debugging/System/Request.class/object_walker.yaml: 41 | created_on: !ruby/object:ActiveSupport::TimeWithZone 42 | utc: &8 2018-08-22 12:26:45.217636000 Z 43 | zone: *2 44 | time: *8 45 | updated_on: !ruby/object:ActiveSupport::TimeWithZone 46 | utc: &9 2018-08-22 12:26:45.226456000 Z 47 | zone: *2 48 | time: *9 49 | size: 210 50 | sha1: gULZ5GFRmSoboOyMooW8D6ub7CE= 51 | Investigative_Debugging/Discovery/__namespace__.yaml: 52 | created_on: !ruby/object:ActiveSupport::TimeWithZone 53 | utc: &10 2018-08-22 12:26:44.027484000 Z 54 | zone: *2 55 | time: *10 56 | updated_on: !ruby/object:ActiveSupport::TimeWithZone 57 | utc: &11 2018-08-22 12:26:44.027484000 Z 58 | zone: *2 59 | time: *11 60 | size: 148 61 | sha1: vAKxuorIM7oGD4b2j9UKMGsZNMg= 62 | Investigative_Debugging/Discovery/ObjectWalker.class/__class__.yaml: 63 | created_on: !ruby/object:ActiveSupport::TimeWithZone 64 | utc: &12 2018-08-22 12:26:44.205290000 Z 65 | zone: *2 66 | time: *12 67 | updated_on: !ruby/object:ActiveSupport::TimeWithZone 68 | utc: &13 2018-08-22 12:26:44.222080000 Z 69 | zone: *2 70 | time: *13 71 | size: 2868 72 | sha1: MCghF0Cogx4gEKyQOYKFBosv12o= 73 | Investigative_Debugging/Discovery/ObjectWalker.class/configuration.yaml: 74 | created_on: !ruby/object:ActiveSupport::TimeWithZone 75 | utc: &14 2018-08-22 12:26:44.443504000 Z 76 | zone: *2 77 | time: *14 78 | updated_on: !ruby/object:ActiveSupport::TimeWithZone 79 | utc: &15 2018-08-23 10:45:50.097443000 Z 80 | zone: *2 81 | time: *15 82 | size: 2520 83 | sha1: wWybcNK0RiaHNehpyzTLfcBZwiA= 84 | Investigative_Debugging/Discovery/ObjectWalker.class/object_walker.yaml: 85 | created_on: !ruby/object:ActiveSupport::TimeWithZone 86 | utc: &16 2018-08-22 12:26:44.516788000 Z 87 | zone: *2 88 | time: *16 89 | updated_on: !ruby/object:ActiveSupport::TimeWithZone 90 | utc: &17 2018-08-22 12:26:44.527618000 Z 91 | zone: *2 92 | time: *17 93 | size: 187 94 | sha1: ENXKSlZ5UWCpTefLKnDv2HyAUCw= 95 | Investigative_Debugging/Discovery/ObjectWalker.class/__methods__/object_walker.rb: 96 | created_on: &20 !ruby/object:ActiveSupport::TimeWithZone 97 | utc: &18 2018-08-22 12:26:44.595523000 Z 98 | zone: *2 99 | time: *18 100 | updated_on: &21 !ruby/object:ActiveSupport::TimeWithZone 101 | utc: &19 2018-08-23 10:51:27.619046000 Z 102 | zone: *2 103 | time: *19 104 | size: 37189 105 | sha1: AJeNeGr6Af/Mbdy21+kHGj6R0eM= 106 | Investigative_Debugging/Discovery/ObjectWalker.class/__methods__/object_walker.yaml: 107 | created_on: *20 108 | updated_on: *21 109 | size: 209 110 | sha1: OZw9nQl5YB/WXs/NXip8galRGt4= 111 | -------------------------------------------------------------------------------- /sample_whitelists: -------------------------------------------------------------------------------- 1 | # 2 | # Nice general purpose 3 | # 4 | {'MiqAeServiceServiceTemplateProvisionTask': ['source', 'destination', 'miq_request', 'miq_request_tasks', 'service_resource'], 5 | 'MiqAeServiceServiceTemplateProvisionRequest': ['miq_request', 'miq_request_tasks', 'requester', 'resource', 'source'], 6 | 'MiqAeServiceServiceTemplate': ['service_resources'], 7 | 'MiqAeServiceServiceResource': ['resource', 'service_template'], 8 | 'MiqAeServiceMiqProvisionRequest': ['miq_request', 'miq_request_tasks', 'miq_provisions', 'requester', 'resource', 'source', 'vm_template'], 9 | 'MiqAeServiceMiqProvisionRequestTemplate': ['miq_request', 'miq_request_tasks'], 10 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Provision': ['source', 'destination', 'miq_provision_request', 'miq_request', 'miq_request_task', 'vm', 'vm_template'], 11 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Provision': ['ALL'], 12 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_Vm': ['availability_zone', 'cloud_network', 'cloud_subnet', 'cloud_subnets'], 13 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Vm': ['ems_cluster', 'ems_folder', 'resource_pool', 'ext_management_system', 'storage', 'service', 'hardware', 'operating_system'], 14 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm': ['ems_cluster', 'resource_pool', 'ext_management_system', 'storage', 'service', 'hardware'], 15 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_Vm': ['availability_zone', 'cloud_networks', 'cloud_subnets'], 16 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_Vm': ['flavor', 'floating_ip'], 17 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Vm': ['miq_provision', 'network_ports', 'network_routers', 'operating_system'], 18 | 'MiqAeServiceHardware': ['nics', 'guest_devices', 'ports', 'vm' ], 19 | 'MiqAeServiceUser': ['current_group'], 20 | 'MiqAeServiceGuestDevice': ['hardware', 'lan', 'network']} 21 | 22 | # 23 | # Whitelist for dumping VM details from a button 24 | # 25 | { 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm': ['hardware', 'host', 'storage'], 26 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Vm': ['hardware', 'host', 'storage'], 27 | 'MiqAeServiceHardware': ['nics', 'guest_devices', 'ports', 'storage_adapters' ], 28 | 'MiqAeServiceGuestDevice': ['hardware', 'lan', 'network'] } 29 | 30 | # 31 | # Whitelist for dumping automation requests and tasks 32 | # 33 | { 'MiqAeServiceAutomationTask': ['automation_request'] } 34 | # 35 | # Whitelist for dumping provisioning requests and tasks (e.g. call from the Infrastructure VM Provisioning State Machine) 36 | # 37 | { 'MiqAeServiceMiqProvisionRequest': ['miq_request', 'miq_request_tasks', 'miq_provisions', 'requester', 'resource', 'source', 'vm_template'], 38 | 'MiqAeServiceMiqProvisionRequestTemplate': ['ALL'], 39 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Provision': ['ALL'], 40 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Provision': ['ALL'], 41 | 'MiqAeServiceUser': ['current_group'] } 42 | # 43 | # Whitelist for dumping service requests (e.g. call from the Service Provision State Machone) 44 | # 45 | { 'MiqAeServiceServiceTemplateProvisionTask': ['source', 'destination', 'miq_request', 'miq_request_tasks', 'service_resource'], 46 | 'MiqAeServiceServiceTemplateProvisionRequest': ['miq_request', 'miq_request_tasks', 'requester', 'resource', 'source'], 47 | 'MiqAeServiceServiceTemplate': ['service_resources'], 48 | 'MiqAeServiceServiceResource': ['resource', 'service_template'] } 49 | 50 | # 51 | # Sample blacklist 52 | # 53 | { 54 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager': ['ems_events'], 55 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager': ['ems_events'], 56 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager': ['ems_events'], 57 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager': ['ems_events'], 58 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager': ['ems_events'], 59 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager': ['ems_events'], 60 | 'MiqAeServiceManageIQ_Providers_Openstack_InfraManager': ['ems_events'], 61 | 'MiqAeServiceManageIQ_Providers_Microsoft_InfraManager': ['ems_events'], 62 | 'MiqAeServiceManageIQ_Providers_Foreman_ConfigurationManager': ['ems_events'], 63 | 'MiqAeServiceManageIQ_Providers_AnsibleTower_ConfigurationManager': ['ems_events'], 64 | 'MiqAeServiceEmsVmware': ['ems_events'], 65 | 'MiqAeServiceEmsRedhat': ['ems_events'], 66 | 'MiqAeServiceEmsCluster': ['all_vms', 'vms', 'ems_events'], 67 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_Vm': ['guest_applications', 'ems_events'], 68 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Vm': ['guest_applications', 'ems_events'], 69 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm': ['guest_applications', 'ems_events'], 70 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_Vm': ['guest_applications', 'ems_events'], 71 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_Vm': ['guest_applications', 'ems_events'], 72 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Vm': ['guest_applications', 'ems_events'], 73 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Host': ['guest_applications', 'ems_events'], 74 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Host': ['guest_applications', 'ems_events'] 75 | } -------------------------------------------------------------------------------- /Investigative_Debugging/System/Request.class/__class__.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | object_type: class 3 | version: 1.0 4 | object: 5 | attributes: 6 | description: Automation Requests 7 | display_name: 8 | name: Request 9 | type: 10 | inherits: 11 | visibility: 12 | owner: 13 | schema: 14 | - field: 15 | aetype: assertion 16 | name: guard 17 | display_name: 18 | datatype: 19 | priority: 1 20 | owner: 21 | default_value: 22 | substitute: true 23 | message: create 24 | visibility: 25 | collect: 26 | scope: 27 | description: 28 | condition: 29 | on_entry: 30 | on_exit: 31 | on_error: 32 | max_retries: 33 | max_time: 34 | - field: 35 | aetype: method 36 | name: on_entry 37 | display_name: 38 | datatype: 39 | priority: 2 40 | owner: 41 | default_value: 42 | substitute: true 43 | message: create 44 | visibility: 45 | collect: 46 | scope: 47 | description: 48 | condition: 49 | on_entry: 50 | on_exit: 51 | on_error: 52 | max_retries: 53 | max_time: 54 | - field: 55 | aetype: relationship 56 | name: rel1 57 | display_name: 58 | datatype: 59 | priority: 3 60 | owner: 61 | default_value: 62 | substitute: true 63 | message: create 64 | visibility: 65 | collect: 66 | scope: 67 | description: 68 | condition: 69 | on_entry: 70 | on_exit: 71 | on_error: 72 | max_retries: 73 | max_time: 74 | - field: 75 | aetype: method 76 | name: meth1 77 | display_name: 78 | datatype: 79 | priority: 4 80 | owner: 81 | default_value: 82 | substitute: true 83 | message: create 84 | visibility: 85 | collect: 86 | scope: 87 | description: 88 | condition: 89 | on_entry: 90 | on_exit: 91 | on_error: 92 | max_retries: 93 | max_time: 94 | - field: 95 | aetype: relationship 96 | name: rel2 97 | display_name: 98 | datatype: 99 | priority: 5 100 | owner: 101 | default_value: 102 | substitute: true 103 | message: create 104 | visibility: 105 | collect: 106 | scope: 107 | description: 108 | condition: 109 | on_entry: 110 | on_exit: 111 | on_error: 112 | max_retries: 113 | max_time: 114 | - field: 115 | aetype: method 116 | name: meth2 117 | display_name: 118 | datatype: 119 | priority: 6 120 | owner: 121 | default_value: 122 | substitute: true 123 | message: create 124 | visibility: 125 | collect: 126 | scope: 127 | description: 128 | condition: 129 | on_entry: 130 | on_exit: 131 | on_error: 132 | max_retries: 133 | max_time: 134 | - field: 135 | aetype: relationship 136 | name: rel3 137 | display_name: 138 | datatype: 139 | priority: 7 140 | owner: 141 | default_value: 142 | substitute: true 143 | message: create 144 | visibility: 145 | collect: 146 | scope: 147 | description: 148 | condition: 149 | on_entry: 150 | on_exit: 151 | on_error: 152 | max_retries: 153 | max_time: 154 | - field: 155 | aetype: method 156 | name: meth3 157 | display_name: 158 | datatype: 159 | priority: 8 160 | owner: 161 | default_value: 162 | substitute: true 163 | message: create 164 | visibility: 165 | collect: 166 | scope: 167 | description: 168 | condition: 169 | on_entry: 170 | on_exit: 171 | on_error: 172 | max_retries: 173 | max_time: 174 | - field: 175 | aetype: relationship 176 | name: rel4 177 | display_name: 178 | datatype: 179 | priority: 9 180 | owner: 181 | default_value: 182 | substitute: true 183 | message: create 184 | visibility: 185 | collect: 186 | scope: 187 | description: 188 | condition: 189 | on_entry: 190 | on_exit: 191 | on_error: 192 | max_retries: 193 | max_time: 194 | - field: 195 | aetype: method 196 | name: meth4 197 | display_name: 198 | datatype: 199 | priority: 10 200 | owner: 201 | default_value: 202 | substitute: true 203 | message: create 204 | visibility: 205 | collect: 206 | scope: 207 | description: 208 | condition: 209 | on_entry: 210 | on_exit: 211 | on_error: 212 | max_retries: 213 | max_time: 214 | - field: 215 | aetype: relationship 216 | name: rel5 217 | display_name: 218 | datatype: 219 | priority: 11 220 | owner: 221 | default_value: 222 | substitute: true 223 | message: create 224 | visibility: 225 | collect: 226 | scope: 227 | description: 228 | condition: 229 | on_entry: 230 | on_exit: 231 | on_error: 232 | max_retries: 233 | max_time: 234 | - field: 235 | aetype: method 236 | name: meth5 237 | display_name: 238 | datatype: 239 | priority: 12 240 | owner: 241 | default_value: 242 | substitute: true 243 | message: create 244 | visibility: 245 | collect: 246 | scope: 247 | description: 248 | condition: 249 | on_entry: 250 | on_exit: 251 | on_error: 252 | max_retries: 253 | max_time: 254 | - field: 255 | aetype: relationship 256 | name: rel6 257 | display_name: 258 | datatype: 259 | priority: 13 260 | owner: 261 | default_value: 262 | substitute: true 263 | message: create 264 | visibility: 265 | collect: 266 | scope: 267 | description: 268 | condition: 269 | on_entry: 270 | on_exit: 271 | on_error: 272 | max_retries: 273 | max_time: 274 | - field: 275 | aetype: method 276 | name: meth6 277 | display_name: 278 | datatype: 279 | priority: 14 280 | owner: 281 | default_value: 282 | substitute: true 283 | message: create 284 | visibility: 285 | collect: 286 | scope: 287 | description: 288 | condition: 289 | on_entry: 290 | on_exit: 291 | on_error: 292 | max_retries: 293 | max_time: 294 | - field: 295 | aetype: relationship 296 | name: rel7 297 | display_name: 298 | datatype: 299 | priority: 15 300 | owner: 301 | default_value: 302 | substitute: true 303 | message: create 304 | visibility: 305 | collect: 306 | scope: 307 | description: 308 | condition: 309 | on_entry: 310 | on_exit: 311 | on_error: 312 | max_retries: 313 | max_time: 314 | - field: 315 | aetype: method 316 | name: meth7 317 | display_name: 318 | datatype: 319 | priority: 16 320 | owner: 321 | default_value: 322 | substitute: true 323 | message: create 324 | visibility: 325 | collect: 326 | scope: 327 | description: 328 | condition: 329 | on_entry: 330 | on_exit: 331 | on_error: 332 | max_retries: 333 | max_time: 334 | - field: 335 | aetype: relationship 336 | name: rel8 337 | display_name: 338 | datatype: 339 | priority: 17 340 | owner: 341 | default_value: 342 | substitute: true 343 | message: create 344 | visibility: 345 | collect: 346 | scope: 347 | description: 348 | condition: 349 | on_entry: 350 | on_exit: 351 | on_error: 352 | max_retries: 353 | max_time: 354 | - field: 355 | aetype: method 356 | name: meth8 357 | display_name: 358 | datatype: 359 | priority: 18 360 | owner: 361 | default_value: 362 | substitute: true 363 | message: create 364 | visibility: 365 | collect: 366 | scope: 367 | description: 368 | condition: 369 | on_entry: 370 | on_exit: 371 | on_error: 372 | max_retries: 373 | max_time: 374 | - field: 375 | aetype: relationship 376 | name: rel9 377 | display_name: 378 | datatype: 379 | priority: 19 380 | owner: 381 | default_value: 382 | substitute: true 383 | message: create 384 | visibility: 385 | collect: 386 | scope: 387 | description: 388 | condition: 389 | on_entry: 390 | on_exit: 391 | on_error: 392 | max_retries: 393 | max_time: 394 | - field: 395 | aetype: method 396 | name: on_exit 397 | display_name: 398 | datatype: 399 | priority: 20 400 | owner: 401 | default_value: 402 | substitute: true 403 | message: create 404 | visibility: 405 | collect: 406 | scope: 407 | description: 408 | condition: 409 | on_entry: 410 | on_exit: 411 | on_error: 412 | max_retries: 413 | max_time: 414 | -------------------------------------------------------------------------------- /object_walker_reader.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # object_walker_reader - extract the latest (no arguments), or a selected object_walker dump from automation.log or other renamed or 4 | # saved log file. 5 | # 6 | #Usage: object_walker_reader.rb [options] 7 | # -l, --list list object_walker dumps in the file 8 | # -f, --file filename Full file path to automation.log (if not /var/www/miq/vmdb/log/automation.log) 9 | # -t, --timestamp timestamp Date/time of the object_walker dump to be listed (hint: copy from -l output) 10 | # -d, --diff timestamp1,timestamp2 Date/time of two object_walker dumps to be compared using 'diff' 11 | # -h, --help Displays Help 12 | # 13 | # Examples: 14 | # 15 | # ./object_walker_reader.rb -l 16 | # Found object_walker dump at 2014-09-17T13:28:42.052043 17 | # Found object_walker dump at 2014-09-17T13:34:52.649359 18 | # Found object_walker dump at 2014-09-17T15:06:29.250086 19 | # ... 20 | # 21 | # ./object_walker_reader.rb -l -f /Documents/CloudForms/cf30-automation-log 22 | # Found object_walker dump at 2014-09-18T09:52:28.797868 23 | # Found object_walker dump at 2014-09-18T09:53:31.455892 24 | # ... 25 | # 26 | # ./object_walker_reader.rb -t 2014-09-18T09:44:27.146812 27 | # object_walker 1.0 - EVM Automate Method Started 28 | # object_walker: Dumping $evm.root 29 | # object_walker: $evm.root.ae_provider_category = infrastructure (type: String) 30 | # object_walker: $evm.root.class = Methods (type: String) 31 | # object_walker: $evm.root.instance = object_walker (type: String) 32 | # object_walker: $evm.root['miq_server'] => # (type: DRb::DRbObject) 33 | # | object_walker: $evm.root['miq_server'].build = 20140822170824_3268809 (type: String) 34 | # | object_walker: $evm.root['miq_server'].capabilities = {:vixDisk=>true, :concurrent_miqproxies=>2} (type: Hash) 35 | # | object_walker: $evm.root['miq_server'].cpu_time = 2312.0 (type: Float) 36 | # | object_walker: $evm.root['miq_server'].drb_uri = druby://127.0.0.1:50656 (type: String) 37 | # | object_walker: $evm.root['miq_server'].guid = 5132a574-3d76-11e4-9150-001a4aa80204 (type: String) 38 | # ... 39 | # 40 | # Author: Peter McGowan (pemcg@redhat.com) 41 | # Copyright 2014 Peter McGowan, Red Hat 42 | # 43 | # Revision History 44 | # 45 | # Original 1.0 18-Sep-2014 46 | # 1.1 16-Feb-2015 Updated to change name change from objectWalker to object_walker 47 | # 1.1-1 19-Feb-2015 Changed the object_walker_start_re regex to allow for maj.min-up 48 | # 1.1-2 08-Feb-2015 Changed the regexes to search for 'objectWalker' or 'object_walker' 49 | # 1.1-3 09-Feb-2015 Changed all the regexes this time 50 | # 1.1-4 22-Mar-2015 Fixed the case where a requested timestamp that didn't exist would still dump the last object_walker output 51 | # 1.1-5 09-May-2015 Read automation.log in UTF-8 format 52 | # 1.2 11-May-2015 Re-factored, and added --diff switch 53 | # 1.3 06-Oct-2015 Changed regexes to match pre or post 1.6 format object_walker dumps. No longer print 'object_walker' 54 | # for each line. Strip the ID from 1.6 format object_walker dumps. 55 | # 1.4 02-Nov-2015 Print continuation lines where a value is multi-line 56 | # 1.7 08-Dec-2015 Bumping version to match object_walker 1.7. We now handle the indentation string here 57 | # 1.8 19-Oct-2017 Ignore stack traces in output 58 | # 1.9 12-Mar-2018 Ignore debug traces in automation.log (CFME 5.9.0.22 has many) 59 | # 2.0 22-Aug-2018 Handle object_walker being callable as an embedded method (so method name might not be object_walker) 60 | # 61 | 62 | require 'optparse' 63 | require 'tempfile' 64 | 65 | VERSION = "2.0" 66 | @debug = false 67 | 68 | valid_timestamp_re = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}/ 69 | # 70 | # Match 1.7 format object_walker dumps 71 | # 72 | @debug_line_re = %r{^\[----\] D,} 73 | @non_debug_line_re = %r{^\[----\] [IEW],} 74 | @object_walker_start_re = %r{ 75 | ----\]\ I,\ \[(?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}) 76 | \ .*[Oo]bject_*[Ww]alk.*?(?\#\w+):\ \ \ (?Object\ Walker\ (?.+)\ Starting) 77 | }x 78 | @object_walker_body_re = %r{ 79 | .*AEMethod\ .*>\ [Oo]bject_*[Ww]alk.*?(?\#\w+)*: 80 | \[(?\d+)\]\ (?.*) 81 | | 82 | .*ERROR.*AEMethod\ .*>\ [Oo]bject_*[Ww]alk.*?(?\#\w+)*\ (?.*) 83 | | 84 | (?^[^\[].+$) 85 | }x 86 | @object_walker_end_re = %r{ 87 | [Oo]bject_*[Ww]alk.*?(?\#\w+):\ \ \ (?Object\ Walker\ Complete) 88 | }x 89 | 90 | #------------------------------------------------------------------------------------------------------------- 91 | # Method: list_dumps 92 | # Purpose: List all object_walker dumps found in a given file 93 | # Arguments: file: the opened automation file object 94 | # Returns: nothing 95 | #------------------------------------------------------------------------------------------------------------- 96 | 97 | def list_dumps(file) 98 | file.each do |line| 99 | match = @object_walker_start_re.match(line) 100 | if match 101 | puts "Found object_walker dump at #{match[:timestamp]}" 102 | end 103 | end 104 | end 105 | 106 | #------------------------------------------------------------------------------------------------------------- 107 | # Method: indent_level_to_string 108 | # Purpose: Converts an indentation level integer to the equivalent string 109 | # Arguments: indent_level - an integer 110 | # Returns: the string 111 | #------------------------------------------------------------------------------------------------------------- 112 | 113 | def indent_level_to_string(indent_level) 114 | base_indent = " " 115 | return base_indent += "| " * indent_level.to_i 116 | end 117 | 118 | #------------------------------------------------------------------------------------------------------------- 119 | # Method: find_dump 120 | # Purpose: Writes an object_walker dump to stdout or a temporary file 121 | # Arguments: file: the opened automation file object 122 | # timestamp: an optional timestamp to dump the output from 123 | # tempfile: an optional temp file to write the output to (used with --diff) 124 | # Returns: nothing 125 | #------------------------------------------------------------------------------------------------------------- 126 | 127 | def find_dump(file, timestamp=nil, tempfile=nil) 128 | found_timestamp = false 129 | dump_start = 0 130 | id = nil 131 | version = nil 132 | file.seek(0) 133 | file.each do |line| 134 | match = @object_walker_start_re.match(line) 135 | if match 136 | id = match[:id] 137 | version = match[:version] 138 | unless timestamp.nil? 139 | if timestamp == match[:timestamp] 140 | dump_start = file.pos - line.length 141 | found_timestamp = true 142 | break 143 | end 144 | end 145 | # 146 | # Default is to dump the last object_walker run 147 | # 148 | dump_start = file.pos - line.length 149 | end 150 | end 151 | unless timestamp.nil? 152 | unless found_timestamp 153 | raise "Timestamp: #{timestamp} not found in log file" 154 | end 155 | end 156 | # 157 | # Now rewind to the specified dump output and print the object_walker dump 158 | # 159 | file.seek(dump_start) 160 | indent_level = 0 161 | debug_output = false 162 | file.each do |line| 163 | # 164 | # A rails logger debug line might add many lines of output to automation.log 165 | # and we need to ignore this 166 | # 167 | debug_output = true if @debug_line_re.match(line) 168 | debug_output = false if @non_debug_line_re.match(line) 169 | 170 | match = @object_walker_start_re.match(line) 171 | if match 172 | if match[:id] == id 173 | if tempfile.nil? 174 | puts "#{match[:output]}" 175 | else 176 | tempfile.write("#{match[:output]}\n") 177 | end 178 | end 179 | next 180 | end 181 | match = @object_walker_body_re.match(line) 182 | if match 183 | if match[:continuation_line] && !debug_output 184 | unless line =~ /^\/.*\d+:in .*$/ # ignore stack trace lines 185 | if tempfile.nil? 186 | puts "#{indent_level_to_string(indent_level)}#{line}" 187 | else 188 | tempfile.write("#{indent_level_to_string(indent_level)}#{line}\n") 189 | end 190 | end 191 | elsif match[:id] == id 192 | if match[:indent_level] 193 | indent_level = match[:indent_level] 194 | end 195 | if tempfile.nil? 196 | puts "#{indent_level_to_string(indent_level)}#{match[:output]}" 197 | else 198 | tempfile.write("#{indent_level_to_string(indent_level)}#{match[:output]}\n") 199 | end 200 | end 201 | next 202 | end 203 | match = @object_walker_end_re.match(line) 204 | if match 205 | if match[:id] == id 206 | if tempfile.nil? 207 | puts "#{match[:output]}" 208 | else 209 | tempfile.write("#{match[:output]}\n") 210 | end 211 | break 212 | end 213 | end 214 | end 215 | end 216 | 217 | #------------------------------------------------------------------------------------------------------------- 218 | # Method: diff_dumps 219 | # Purpose: Compares two object_walker dumps using diff 220 | # Arguments: file: the opened automation file object 221 | # timestamp1 & 2: the ojject_walker output timestamps to compare 222 | # Returns: nothing 223 | #------------------------------------------------------------------------------------------------------------- 224 | 225 | def diff_dumps(file, timestamp1, timestamp2) 226 | begin 227 | puts "Getting diff comparison from dumps at #{timestamp1} and #{timestamp2}" 228 | tempfile1 = Tempfile.new('owr-') 229 | tempfile2 = Tempfile.new('owr-') 230 | find_dump(file, timestamp1, tempfile1) 231 | find_dump(file, timestamp2, tempfile2) 232 | tempfile1.close 233 | tempfile2.close 234 | difference = `diff #{tempfile1.path} #{tempfile2.path}` 235 | puts difference 236 | ensure 237 | tempfile1.unlink 238 | tempfile2.unlink 239 | end 240 | end 241 | 242 | begin 243 | options = {:list => false, :filename => nil, :timestamp => nil, :diff => nil} 244 | 245 | parser = OptionParser.new do|opts| 246 | opts.banner = "Usage: object_walker_reader.rb [options]" 247 | opts.on('-l', '--list', 'list object_walker dumps in the file') do 248 | options[:list] = true; 249 | end 250 | opts.on('-f', '--file filename', 'Full file path to automation.log (if not /var/www/miq/vmdb/log/automtion.log)') do |filename| 251 | options[:filename] = filename; 252 | end 253 | opts.on('-t', '--timestamp timestamp', 'Date/time of the object_walker dump to be listed (hint: copy from -l output)') do |timestamp| 254 | options[:timestamp] = timestamp; 255 | end 256 | opts.on('-d', '--diff timestamp1,timestamp2', Array, 'Date/time of two object_walker dumps to be compared using \'diff\'') do |diff_timestamps| 257 | unless diff_timestamps.length == 2 258 | puts "timestamps must be timestamp1,timestamp2 list" 259 | exit! 260 | end 261 | options[:diff] = diff_timestamps 262 | end 263 | opts.on('-h', '--help', 'Displays Help') do 264 | puts opts 265 | exit 266 | end 267 | end 268 | parser.parse! 269 | 270 | if options[:timestamp] 271 | if options[:list] 272 | puts "The options -t and -l should not be used together: use -l to list the dumps in the log file, and -t to select a particular dump timestamp" 273 | exit! 274 | end 275 | unless valid_timestamp_re.match(options[:timestamp]) 276 | puts "Invalid timestamp format" 277 | exit! 278 | end 279 | end 280 | 281 | if options[:filename].nil? 282 | filename = "/var/www/miq/vmdb/log/automation.log" 283 | else 284 | filename = options[:filename] 285 | end 286 | 287 | begin 288 | file = File.new(filename, "r:UTF-8") 289 | rescue => err 290 | puts "Error opening log file: #{err}" 291 | exit! 292 | end 293 | 294 | if options[:list] 295 | list_dumps(file) 296 | elsif options[:diff] 297 | diff_dumps(file, options[:diff][0], options[:diff][1]) 298 | else 299 | find_dump(file, options[:timestamp]) 300 | end 301 | 302 | rescue => err 303 | puts "#{err}" 304 | exit! 305 | ensure 306 | file.close unless file.nil? 307 | end 308 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # object_walker 2 | 3 | One of the challenges when starting out writing CloudForms or ManageIQ automation scripts, is knowing where the objects and attributes are under `$evm.root` that we may need to access. For example, depending on the automation action, we may have an `$evm.root['vm']` object, or we may not. 4 | 5 | This script is an attempt to demystify the object structure that is available at any point in an Automation workflow. 6 | 7 | Calling the script from any point will walk the object hierarchy from `$evm.root` downwards, printing objects and attributes 8 | as it goes, i.e. 9 | 10 | 11 | ``` 12 | Object Walker 1.8 Starting 13 | --- walk_association_policy details --- 14 | walk_association_policy = whitelist 15 | walk_association_whitelist = { 'MiqAeServiceMiqProvisionRequest': ['miq_request','miq_request_tasks','miq_provisions','requester','resource','source','vm_template'], 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Provision': ['source','destination','miq_provision_request','miq_request','miq_request_task','vm','vm_template','tenant'], 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm': ['ems_cluster','ems_folder','resource_pool','service','ext_management_system','storage','hardware','operating_system'], 'MiqAeServiceHardware': ['nics','guest_devices','ports','vm'], 'MiqAeServiceUser': ['current_group'], 'MiqAeServiceGuestDevice': ['hardware','lan','network'] } 16 | --- $evm.current_* details --- 17 | $evm.current_namespace = Bit63/Stuff (type: String) 18 | $evm.current_class = ObjectWalker (type: String) 19 | $evm.current_instance = object_walker (type: String) 20 | $evm.current_method = object_walker (type: String) 21 | $evm.current_message = create (type: String) 22 | $evm.current_object = /Bit63/Stuff/ObjectWalker/object_walker (type: DRb::DRbObject, URI: druby://127.0.0.1:33776) 23 | $evm.current_object.current_field_name = execute (type: String) 24 | $evm.current_object.current_field_type = method (type: String) 25 | --- automation instance hierarchy --- 26 | /ManageIQ/System/Process/AUTOMATION ($evm.root) 27 | | /ManageIQ/infrastructure/VM/Lifecycle/Provisioning 28 | | | /ManageIQ/Infrastructure/VM/Provisioning/Profile/EvmGroup-super_administrator 29 | | | /Bit63/Infrastructure/VM/Provisioning/StateMachines/VMProvision_vm/template ($evm.parent) 30 | | | | /ManageIQ/Infrastructure/VM/Provisioning/StateMachines/Methods/CustomizeRequest 31 | | | | /ManageIQ/Infrastructure/VM/Provisioning/Placement/default 32 | | | | /Bit63/Stuff/ObjectWalker/object_walker ($evm.object) 33 | --- walking $evm.root --- 34 | $evm.root = /ManageIQ/System/Process/AUTOMATION (type: DRb::DRbObject, URI: druby://127.0.0.1:33776) 35 | | --- attributes follow --- 36 | | $evm.root['ae_next_state'] = (type: String) 37 | | $evm.root['ae_provider_category'] = infrastructure (type: String) 38 | | $evm.root['ae_result'] = ok (type: String) 39 | | $evm.root['ae_state'] = WalkObjects (type: String) 40 | | $evm.root['ae_state_retries'] = 0 (type: Fixnum) 41 | | $evm.root['ae_state_started'] = 2016-08-01 09:55:59 UTC (type: String) 42 | | $evm.root['ae_state_step'] = main (type: String) 43 | | $evm.root['ae_status_state'] = on_exit (type: String) 44 | | $evm.root['miq_group'] => # (type: DRb::DRbObject, URI: druby://127.0.0.1:33776) 45 | | | --- attributes follow --- 46 | | | $evm.root['miq_group'].created_on = 2016-05-25 08:09:35 UTC (type: ActiveSupport::TimeWithZone) 47 | | | $evm.root['miq_group'].description = EvmGroup-super_administrator (type: String) 48 | | | $evm.root['miq_group'].filters = nil 49 | | | $evm.root['miq_group'].group_type = system (type: String) 50 | | | $evm.root['miq_group'].id = 2 (type: Fixnum) 51 | | | $evm.root['miq_group'].sequence = 1 (type: Fixnum) 52 | | | $evm.root['miq_group'].settings = nil 53 | | | $evm.root['miq_group'].tenant_id = 1 (type: Fixnum) 54 | | | $evm.root['miq_group'].updated_on = 2016-05-25 08:09:35 UTC (type: ActiveSupport::TimeWithZone) 55 | | | --- end of attributes --- 56 | | | --- virtual columns follow --- 57 | | | $evm.root['miq_group'].allocated_memory = 1073741824 (type: Fixnum) 58 | | | $evm.root['miq_group'].allocated_storage = 42949672960 (type: Fixnum) 59 | | | $evm.root['miq_group'].allocated_vcpu = 1 (type: Fixnum) 60 | ``` 61 | etc. 62 | 63 | 64 | Several of the objects in the Automate model have circular references to themselves either directly or indirectly through 65 | other associations. To prevent the same object being printed multiple times the script records where it's been, and prints: 66 | 67 | ``` 68 | | (object type: MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Vm, object ID: 23) 69 | | | Object MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Vm with ID 23 has already been printed... 70 | ``` 71 | 72 | ## Configuration Instance - NEW! 73 | 74 | object_walker 2.0 comes with a new _configuration_ instance containing the configuration attributes. The configuration instance **must** be called _/Discovery/ObjectWalker/configuration_. 75 | 76 | > **Note** 77 | > 78 | > For backwards compatibility and for invoking _object\_walker_ via `$evm.instantiate` or from an instance relationship, the _object\_walker_ instance still remains, however this no longer contains the configuration attributes. 79 | 80 | ### print\_evm\_object 81 | 82 | print\_evm\_object can be used to toggle whether or not to walk the object structure of the `$evm.object` (i.e current) object. If object\_walker has been invoked from `$evm.instantiate` or from a relationship then `$evm.object` will be object\_walker itself. If object\_walker has been invoked from an embedded method then $evm.object is the calling method's instance. 83 | 84 | ### print\_evm\_parent 85 | 86 | print\_evm\_parent can be used to toggle whether or not to walk the object structure of the `$evm.parent` object. If object\_walker has been invoked from `$evm.instantiate` or from a relationship then `$evm.parent` will be the calling instance. 87 | 88 | ### print\_nil\_values 89 | 90 | Many attributes that get printed have a value of 'nil', i.e. 91 | 92 | ``` 93 | | | $evm.root['user'].userid = admin (type: String) 94 | | | --- end of attributes --- 95 | | | --- virtual columns follow --- 96 | | | $evm.root['user'].allocated_memory = 0 (type: Fixnum) 97 | | | $evm.root['user'].allocated_storage = 0 (type: Fixnum) 98 | | | $evm.root['user'].allocated_vcpu = 0 (type: Fixnum) 99 | | | $evm.root['user'].custom_1 = nil 100 | | | $evm.root['user'].custom_2 = nil 101 | | | $evm.root['user'].custom_3 = nil 102 | | | $evm.root['user'].custom_4 = nil 103 | | | $evm.root['user'].custom_5 = nil 104 | | | $evm.root['user'].custom_6 = nil 105 | | | $evm.root['user'].custom_7 = nil 106 | | | $evm.root['user'].custom_8 = nil 107 | | | $evm.root['user'].custom_9 = nil 108 | | | $evm.root['user'].ldap_group = EvmGroup-super_administrator (type: String) 109 | | | $evm.root['user'].miq_group_description = EvmGroup-super_administrator (type: String) 110 | ``` 111 | 112 | print\_nil\_values can be used to toggle whether or not to include keys that have a nil value in the output dump. There are often many, and including them will usually increase verbosity, but it is sometimes useful to know that a key/attribute exists, even if it currently has no assigned value. 113 | 114 | ### walk\_association\_policy 115 | 116 | Many of the objects that we can walk through are in fact Rails Active Record Associations (object representations of database 117 | records), and we often don't want to print all of them. The script uses a `walk_association_policy`, variable to help decide which associations to traverse; it should have the value of either "whitelist" or "blacklist". This variable defaults to "whitelist" unless overridden by an instance schema attribute called _walk\_association\_policy_ (read as `$evm.object['walk_association_policy']`). 118 | 119 | ### walk\_association\_whitelist 120 | 121 | If `walk_association_policy` = _whitelist_, then object\_walker will only traverse associations of objects that are explicitly 122 | mentioned in the `walk_association_whitelist` JSON-like hash (either defined in the instance schema, or in a service dialog). The string "ALL" can be used to walk all associations of an object type. A typical whitelist hash is as follows: 123 | 124 | ``` 125 | {"MiqAeServiceServiceTemplateProvisionTask":["source","destination","miq_request"], 126 | "MiqAeServiceServiceTemplateProvisionRequest":["miq_request","miq_request_tasks","requester","source"], 127 | "MiqAeServiceServiceTemplate":["service_resources"], 128 | "MiqAeServiceServiceResource":["resource","service_template"], 129 | "MiqAeServiceMiqProvisionRequest":["miq_request","miq_request_tasks","miq_provisions"], 130 | "MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Provision":["ALL"], 131 | "MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm":["ems_cluster","ems_folder","resource_pool"], 132 | "MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Vm":["key_pairs","security_groups","tenant","flavor"], 133 | "MiqAeServiceHardware":["ALL"], 134 | "MiqAeServiceUser":["current_group"], 135 | "MiqAeServiceGuestDevice":["hardware","lan","network"]} 136 | ``` 137 | 138 | The hash enables us to carefully control what is traversed and printed. If object\_walker finds an association that isn't in the hash, it will print a line similar to: 139 | 140 | ``` 141 | $evm.root['user'].current_tenant (type: Association) 142 | *** not walking: 'current_tenant' isn't in the walk_association_whitelist hash for MiqAeServiceUser *** 143 | ``` 144 | 145 | If we decide to explore and dump this _current\_tenant_ association, we edit the hash to add its name to the list associated with the 146 | object type. In the example whitelist hash above, we would add to the list defined by the _MiqAeServiceUser_ key, for example: 147 | 148 | ``` 149 | "MiqAeServiceUser":["current_group", "current_tenant"], 150 | ``` 151 | 152 | ### walk\_association\_blacklist 153 | 154 | if `walk_association_policy` = _blacklist_, then object\_walker will traverse all associations of all objects, _except_ those that 155 | are explicitly mentioned in the `walk_association_blacklist` hash. This enables us to run a more exploratory dump, at the 156 | cost of a **much** more verbose output. The format of the blacklist is the same as the whitelist, and the string "ALL" can be used to avoid walking _any_ associations of an object type. 157 | 158 | --- 159 | 160 | **Note:** the _object\_associations_ file in the repository lists all associations of all MiqAeService objects, and can be used as a reference. The _sample\_whitelists_ file contains examples of typical whitelists that could be used for various investigations. 161 | 162 | --- 163 | 164 | ### Defining the Hashes in the _configuration_ Instance Schema 165 | 166 | The _walk\_association\_whitelist_ and _walk\_association\_blacklist_ hash definitions are JSON-like hashes, but either single or double quotes can be used, and the quotes don't need to be escaped by backslashes. We define the _walk\_association\_policy_, _walk\_association\_whitelist_ and _walk\_association\_blacklist_ as attributes of data type _String_ in the class and instance schema. 167 | 168 | The following sceenshot is an example of typical schema attribute definitions: 169 | 170 | ![Screenshot 01](images/screenshot01.jpg) 171 | 172 | ### Defining the Hashes in a Service Dialog 173 | 174 | When exploring the object model, we frequently update the walk\_association\_whitelist or walk\_association\_blacklist schema attributes as new associations are being explored. 175 | 176 | To avoid having to edit the instance attributes in such circumstances, a service dialog can be created containing a text area box element named _walk\_association\_whitelist_ or _walk\_association\_blacklist_. Any valid JSON-like whitelist hash entered into this dialog field will be used as a run-time override of the walk\_association\_whitelist or walk\_association\_blacklist defined in the instance schema. The object\_walker instance can then be called from a button, configured to display the dialog. 177 | 178 | ![Screenshot 07](images/screenshot02.jpg) 179 | 180 | ## Installation 181 | 182 | There are two ways of installing object\_walker. 183 | 184 | ### Importing the datastore via git 185 | 186 | If you have the _Git Repositories Owner_ server role enabled on an appliance you can git import object\_walker straight into the automate datastore. Use the URL https://github.com/pemcg/object_walker 187 | 188 | ![Screenshot 09](images/screenshot09.jpg) 189 | 190 | This will import a domain called `Investigative_Debugging` containing the `Discovery` namespace, and its class, instance and method. The class schema contains a sample general-purpose whitelist. 191 | 192 | ### Importing the domain from a zip file 193 | 194 | Copy the investigative_debugging.zip datastore export to your local system, and import it from the *Automate -> Import / Export* menu. This will import the _Investigative\_Debugging_ domain. 195 | 196 | ## Upgrading to 2.0 197 | 198 | The schema of the _ObjectWalker_ class has changed. If any customisations have been made to an _object\_walker_ v1.x instance (such as an updated whitelist) these should be saved, and the old _ObjectWalker_ class and associated _object\_walker_ v1.x instance and method should be deleted. Once _object\_walker_ v2.0 has been installed the previous modifications can be re-applied to a copy of the new _configuration_ instance. 199 | 200 | 201 | ## Invoking object\_walker 202 | 203 | There are three ways of invoking object\_walker. 204 | 205 | ### As an Embedded Method - NEW! 206 | 207 | object\_walker can be callable as an embedded method with CloudForms 4.6 / ManageIQ _Gaprindashvili_ . The calling method should embed the _/Discovery/ObjectWalker/object\_walker_ "library" method, and invoke it using: 208 | 209 | `Investigative_Debugging::Discovery::ObjectWalker.walk_objects` 210 | 211 | For example: 212 | 213 | ![Screenshot 08](images/screenshot10.jpg) 214 | 215 | Calling object\_walker in this way gives the most accurate view of the `$evm` object structure in relation to the calling method. 216 | 217 | ### From an Instance Relationship 218 | 219 | An object\_walker dump can be obtained by simply calling the _/Discovery/ObjectWalker/object\_walker_ instance from a relationship in any instance in the automation namespace. 220 | 221 | For example if we wish to examine the `$evm.root` object structure part-way through a VM provisioning workflow, we could add a call to object\_walker from a state in the VM Provision State Machine, as follows: 222 | 223 | ![Screenshot 07](images/screenshot07.jpg) 224 | 225 | If we simply wished to examine the object structure related to a VM object, we could call object_walker from a button on a VM in the WebUI, as follows: 226 | 227 | ![Screenshot 08](images/screenshot08.jpg) 228 | 229 | ### From $evm.instantiate 230 | 231 | object\_walker can be called in-line from another automation method, using `$evm.instantiate`, as follows: 232 | 233 | ``` 234 | $evm.instantiate('/Discovery/ObjectWalker/object_walker') 235 | ``` 236 | 237 | ## Reading the Output 238 | 239 | Although we could inspect the lines printed by object_walker by following _automation.log_, the preferred way to read the output is to install _object\_walker\_reader.rb_ from the repository to the CloudForms/ManageIQ appliance. 240 | 241 | This can be done either using RPM as follows: 242 | 243 | ``` 244 | rpm -ivh https://github.com/pemcg/object_walker/raw/master/object_walker_reader-.noarch.rpm 245 | ``` 246 | 247 | ...or by copying the object\_walker\_reader-\.tar.gz to /root on an appliance and unpacking it. 248 | 249 | object\_walker\_reader.rb formats the output, and gives us several options for selecting various object\_walker dumps. 250 | 251 | ### object\_walker\_reader 252 | 253 | Use object\_walker\_reader to extract the latest (no arguments), or a selected object_walker dump from _automation.log_ or other 254 | renamed or saved log file. 255 | 256 | ``` 257 | Usage: object_walker_reader.rb [options] 258 | -l, --list list object_walker dumps in the file 259 | -f, --file filename Full file path to automation.log (if not /var/www/miq/vmdb/log/automtion.log) 260 | -t, --timestamp timestamp Date/time of the object_walker dump to be listed (hint: copy from -l output) 261 | -d, --diff timestamp1,timestamp2 Date/time of two object_walker dumps to be compared using 'diff' 262 | -h, --help Displays Help Displays Help 263 | ``` 264 | 265 | #### Examples: 266 | 267 | ##### Listing object\_walker dumps using the '-l' switch 268 | 269 | ``` 270 | ./object_walker_reader.rb -l 271 | Found object_walker dump at 2014-09-17T13:28:42.052043 272 | Found object_walker dump at 2014-09-17T13:34:52.649359 273 | Found object_walker dump at 2014-09-17T15:06:29.250086 274 | Found object_walker dump at 2014-09-17T15:22:46.034628 275 | Found object_walker dump at 2014-09-18T07:56:08.201025 276 | ... 277 | ``` 278 | 279 | ##### Listing dumps in a non-default (i.e. copied from another system) log file using the '-f' switch 280 | 281 | ``` 282 | ./object_walker_reader.rb -l -f /Documents/CloudForms/cf30-automation-log 283 | Found object_walker dump at 2014-09-18T09:52:28.797868 284 | Found object_walker dump at 2014-09-18T09:53:31.455892 285 | Found object_walker dump at 2014-09-18T10:05:39.040744 286 | Found object_walker dump at 2014-09-18T12:00:59.142460 287 | ... 288 | ``` 289 | 290 | ##### Dumping a particular object\_walker output by timestamp using the '-t' switch 291 | 292 | ``` 293 | ~/object_walker_reader.rb -t 2016-08-02T09:17:06.722068 294 | Object Walker 1.8 Starting 295 | *** detected 'print_nil_values = false' so attributes with nil values will not be printed *** 296 | --- walk_association_policy details --- 297 | walk_association_policy = whitelist 298 | walk_association_whitelist = {"MiqAeServiceUser": ["current_group", "current_tenant"]} 299 | --- $evm.current_* details --- 300 | $evm.current_namespace = Bit63/stuff (type: String) 301 | $evm.current_class = objectwalker (type: String) 302 | $evm.current_instance = object_walker (type: String) 303 | $evm.current_method = object_walker (type: String) 304 | $evm.current_message = create (type: String) 305 | $evm.current_object = /Bit63/stuff/objectwalker/object_walker (type: DRb::DRbObject, URI: druby://127.0.0.1:45024) 306 | $evm.current_object.current_field_name = execute (type: String) 307 | $evm.current_object.current_field_type = method (type: String) 308 | --- automation instance hierarchy --- 309 | /ManageIQ/SYSTEM/PROCESS/Request ($evm.root) 310 | | /ManageIQ/System/Request/call_instance ($evm.parent) 311 | | | /Bit63/stuff/objectwalker/object_walker ($evm.object) 312 | --- walking $evm.root --- 313 | $evm.root = /ManageIQ/SYSTEM/PROCESS/Request (type: DRb::DRbObject, URI: druby://127.0.0.1:45024) 314 | | --- attributes follow --- 315 | | $evm.root['ae_provider_category'] = infrastructure (type: String) 316 | ``` 317 | 318 | ##### Comparing the output from two object\_walker dumps using the '-d' switch 319 | 320 | ``` 321 | ./object_walker_reader.rb -d 2015-05-11T14:41:58.031661,2015-05-11T14:42:08.186930 322 | Getting diff comparison from dumps at 2015-05-11T14:41:58.031661 and 2015-05-11T14:42:08.186930 323 | 6c6 324 | < object_walker: $evm.current_object = /Bit63/Discovery/ObjectWalker/default (type: DRb::DRbObject, URI: druby://127.0.0.1:51860) 325 | --- 326 | > object_walker: $evm.current_object = /Bit63/Discovery/ObjectWalker/default (type: DRb::DRbObject, URI: druby://127.0.0.1:54749) 327 | 10c10 328 | < object_walker: $evm.root = /Bit63/Service/Provisioning/StateMachines/ServiceProvision_Template/CatalogItemInitialization (type: DRb::DRbObject, URI: druby://127.0.0.1:51860) 329 | --- 330 | > object_walker: $evm.root = /Bit63/Service/Provisioning/StateMachines/ServiceProvision_Template/CatalogItemInitialization (type: DRb::DRbObject, URI: druby://127.0.0.1:54749) 331 | 12c12 332 | < object_walker: $evm.root['ae_state'] = pre1 (type: String) 333 | --- 334 | > object_walker: $evm.root['ae_state'] = pre3 (type: String) 335 | 14c14 336 | < object_walker: $evm.root['ae_state_started'] = 2015-05-11 14:41:56 UTC (type: String) 337 | --- 338 | > object_walker: $evm.root['ae_state_started'] = 2015-05-11 14:42:07 UTC (type: String) 339 | ... 340 | ``` 341 | 342 | -------------------------------------------------------------------------------- /object_walker.rb: -------------------------------------------------------------------------------- 1 | module Investigative_Debugging 2 | module Discovery 3 | class ObjectWalker 4 | # 5 | # Can be called from anywhere in the CloudForms/ManageIQ automation namespace, and will walk the automation object structure starting from $evm.root 6 | # and dump (to automation.log) its attributes, any objects found, their attributes, virtual columns, and associations, and so on. 7 | # 8 | # Author: Peter McGowan (pemcg@redhat.com) 9 | # Copyright 2018 Peter McGowan, Red Hat 10 | # 11 | require 'active_support/core_ext/string' 12 | require 'securerandom' 13 | require 'json' 14 | 15 | VERSION = "2.0" 16 | MAX_RECURSION_LEVEL = 7 17 | $debug = false 18 | $print_methods = true 19 | 20 | def self.walk_objects 21 | begin 22 | $recursion_level = 0 23 | $object_recorder = {} 24 | $service_model_base_supports_taggable = false 25 | # 26 | # Read our configuration instance to get the variables 27 | # 28 | instance = $evm.instantiate('/Discovery/ObjectWalker/configuration') 29 | if instance.nil? 30 | $evm.log(:error, "*** Instance /Discovery/ObjectWalker/configuration not found in Datastore ***") 31 | exit MIQ_ERROR 32 | end 33 | # 34 | # We need to record the instance methods of the MiqAeMethodService::MiqAeServiceModelBase class so that we can 35 | # subtract this list from the methods we discover for each object 36 | # 37 | $service_mode_base_instance_methods = [] 38 | # 39 | # Change $max_recursion_level to adjust the depth of recursion that object_walker traverses through the objects 40 | # 41 | $max_recursion_level = instance['max_recursion_level'] || MAX_RECURSION_LEVEL 42 | # 43 | # $print_evm_object can be used to toggle whether or not to walk the object structure of the $evm.object (i.e current) object 44 | # If object_walker has been $evm.instantiated or invoked from a relationship then $evm.object will be object_walker itself. If 45 | # object_walker has been invoked from an embedded method then $evm.object is the calling method's instance. 46 | # 47 | $print_evm_object = instance['print_evm_object'].nil? ? false : instance['print_evm_object'] 48 | unless [FalseClass, TrueClass].include? $print_evm_object.class 49 | $evm.log(:error, "*** print_evm_object must be a boolean value ***") 50 | exit MIQ_ERROR 51 | end 52 | # 53 | # $print_evm_parent can be used to toggle whether or not to walk the object structure of the $evm.parent object 54 | # If object_walker has been $evm.instantiated or invoked from a relationship then $evm.parent will be the calling 55 | # instance 56 | # 57 | $print_evm_parent = instance['print_evm_parent'].nil? ? false : instance['print_evm_parent'] 58 | unless [FalseClass, TrueClass].include? $print_evm_parent.class 59 | $evm.log(:error, "*** print_evm_parent must be a boolean value ***") 60 | exit MIQ_ERROR 61 | end 62 | # 63 | # $print_nil_values can be used to toggle whether or not to include keys that have a nil value in the 64 | # output dump. There are often many, and including them will usually increase verbosity, but it is 65 | # sometimes useful to know that a key/attribute exists, even if it currently has no assigned value. 66 | # 67 | $print_nil_values = instance['print_nil_values'].nil? ? true : instance['print_nil_values'] 68 | unless [FalseClass, TrueClass].include? $print_nil_values.class 69 | $evm.log(:error, "*** print_nil_values must be a boolean value ***") 70 | exit MIQ_ERROR 71 | end 72 | # 73 | # $walk_association_policy should have the value of either 'whitelist' or 'blacklist'. This will determine whether we either 74 | # walk all associations _except_ those in the walk_association_blacklist hash, or _only_ the associations in the 75 | # walk_association_whitelist hash 76 | # 77 | $walk_association_policy = instance['walk_association_policy'] || 'whitelist' 78 | # 79 | # if $walk_association_policy = 'whitelist', then object_walker will only traverse associations of objects that are explicitly 80 | # mentioned in the $walk_association_whitelist hash. This enables us to carefully control what is dumped. If object_walker finds 81 | # an association that isn't in the hash, it will print a line similar to: 82 | # 83 | # $evm.root['user'].current_tenant (type: Association) 84 | # *** not walking: 'current_tenant' isn't in the walk_association_whitelist hash for MiqAeServiceUser *** 85 | # 86 | # If you wish to explore and dump this association, edit the hash to add the association name to the list associated with the object type. The string 87 | # 'ALL' can be used to walk all associations of an object type 88 | # 89 | dialog_walk_association_whitelist = ($evm.root['dialog_walk_association_whitelist'] != '') ? $evm.root['dialog_walk_association_whitelist'] : nil 90 | walk_association_whitelist = dialog_walk_association_whitelist || instance['walk_association_whitelist'] 91 | # 92 | # if $walk_association_policy = 'blacklist', then object_walker will traverse all associations of all objects, except those 93 | # that are explicitly mentioned in the $walk_association_blacklist hash. This enables us to run a more exploratory dump, at the cost of a 94 | # much more verbose output. The string 'ALL' can be used to prevent walking any associations of an object type 95 | # 96 | # You have been warned, using a blacklist walk_association_policy produces a lot of output! 97 | # 98 | dialog_walk_association_blacklist = ($evm.root['dialog_walk_association_blacklist'] != '') ? $evm.root['dialog_walk_association_blacklist'] : nil 99 | walk_association_blacklist = dialog_walk_association_blacklist || instance['walk_association_blacklist'] 100 | # 101 | # Generate a random string to identify this object_walker dump 102 | # 103 | randomstring = SecureRandom.hex(4).upcase 104 | $method = "object_walker##{randomstring}" 105 | 106 | $evm.log("info", "#{$method}: Object Walker #{VERSION} Starting") 107 | print_line(0, "*** detected 'print_nil_values = false' so attributes with nil values will not be printed ***") if !$print_nil_values 108 | # 109 | # If we're dumping object methods, then we need to find out the methods of the 110 | # MiqAeMethodService::MiqAeServiceModelBase class so that we can subtract them from the method list 111 | # returned from each object. We know that MiqAeServiceModelBase is the superclass of 112 | # MiqAeMethodService::MiqAeServiceMiqServer, so we can get what we're after via $evm.root['miq_server'] 113 | # 114 | miq_server = $evm.root['miq_server'] rescue nil 115 | unless miq_server.nil? 116 | if miq_server.method_missing(:class).superclass.name == "MiqAeMethodService::MiqAeServiceModelBase" 117 | $service_mode_base_instance_methods = miq_server.method_missing(:class).superclass.instance_methods.map { |x| x.to_s } 118 | else 119 | $evm.log("error", "#{$method} Unexpected parent class of $evm.root['miq_server']: " \ 120 | "#{miq_server.method_missing(:class).superclass.name}") 121 | $print_methods = false 122 | end 123 | else 124 | $evm.log("error", "#{$method} $evm.root['miq_server'] doesn't exist") 125 | $print_methods = false 126 | end 127 | $service_model_base_supports_taggable = true if $service_mode_base_instance_methods.include?('taggable?') 128 | 129 | print_line(0, "--- walk_association_policy details ---") 130 | print_line(0, "walk_association_policy = #{$walk_association_policy}") 131 | case $walk_association_policy 132 | when 'whitelist' 133 | if walk_association_whitelist.nil? 134 | $evm.log(:error, "*** walk_association_whitelist not found, please define one as an instance attribute or a dialog variable ***") 135 | exit MIQ_ERROR 136 | else 137 | $walk_association_whitelist = JSON.parse(walk_association_whitelist.gsub(/\s/,'').gsub(/(? err 217 | $evm.log("error", "#{$method} (object_walker) - Invalid JSON string passed as #{$walk_association_policy}") 218 | $evm.log("error", "#{$method} (object_walker) - Err: #{err.inspect}") 219 | exit MIQ_ERROR 220 | rescue => err 221 | $evm.log("error", "#{$method} (object_walker) - [#{err}]\n#{err.backtrace.join("\n")}") 222 | exit MIQ_ERROR 223 | end 224 | end 225 | 226 | private 227 | 228 | def self.walk_automation_objects(service_object) 229 | automation_object = Struct::ServiceObject.new(service_object.to_s, "", Array.new) 230 | if service_object.to_s == $evm.root.to_s 231 | automation_object.position = 'root' 232 | elsif service_object.to_s == $evm.parent.to_s 233 | automation_object.position = 'parent' 234 | elsif service_object.to_s == $evm.object.to_s 235 | automation_object.position = 'object' 236 | end 237 | offspring = service_object.children 238 | unless offspring.nil? || (offspring.kind_of?(Array) and offspring.length.zero?) 239 | Array.wrap(offspring).each do |child| 240 | automation_object.children << walk_automation_objects(child) 241 | end 242 | end 243 | return automation_object 244 | end 245 | 246 | def self.print_automation_objects(indent_level, hierarchy) 247 | case hierarchy.position 248 | when 'root' 249 | print_line(indent_level, "#{hierarchy.obj_name} ($evm.root)") 250 | when 'parent' 251 | print_line(indent_level, "#{hierarchy.obj_name} ($evm.parent)") 252 | when 'object' 253 | print_line(indent_level, "#{hierarchy.obj_name} ($evm.object)") 254 | else 255 | print_line(indent_level, "#{hierarchy.obj_name}") 256 | end 257 | indent_level += 1 258 | hierarchy.children.each do |child| 259 | print_automation_objects(indent_level, child) 260 | end 261 | end 262 | 263 | def self.print_line(indent_level, string) 264 | $evm.log("info", "#{$method}:[#{indent_level.to_s}] #{string}") 265 | end 266 | 267 | def self.type(object) 268 | if object.is_a?(DRb::DRbObject) 269 | string = "(type: #{object.class}, URI: #{object.__drburi()})" 270 | else 271 | string = "(type: #{object.class})" 272 | end 273 | return string 274 | end 275 | 276 | def self.ping_attr(this_object, attribute) 277 | value = "" 278 | format_string = "." 279 | begin 280 | # 281 | # See if it's an attribute that we access using '.attribute' 282 | # 283 | value = this_object.method_missing(:send, attribute) 284 | format_string = ".#{attribute}" 285 | rescue NoMethodError 286 | # 287 | # Seems not, let's try to access as if it's a hash value 288 | # 289 | value = this_object[attribute] 290 | format_string = "['#{attribute}']" 291 | end 292 | return {:format_string => format_string, :value => value} 293 | end 294 | 295 | def self.str_or_sym(value) 296 | value_as_string = "" 297 | if value.is_a?(Symbol) 298 | value_as_string = ":#{value}" 299 | else 300 | value_as_string = "\'#{value}\'" 301 | end 302 | return value_as_string 303 | end 304 | 305 | def self.print_attributes(object_string, this_object) 306 | begin 307 | # 308 | # Print the attributes of this object 309 | # 310 | if this_object.respond_to?(:attributes) 311 | print_line($recursion_level, "Debug: this_object.inspected = #{this_object.inspect}") if $debug 312 | if this_object.attributes.respond_to?(:keys) 313 | if this_object.attributes.keys.length > 0 314 | print_line($recursion_level, "--- attributes follow ---") 315 | this_object.attributes.keys.sort.each do |attribute_name| 316 | attribute_value = this_object.attributes[attribute_name] 317 | if attribute_name != "options" 318 | if attribute_value.is_a?(DRb::DRbObject) 319 | if attribute_value.method_missing(:class).to_s =~ /^MiqAeMethodService.*/ 320 | print_line($recursion_level, 321 | "#{object_string}[\'#{attribute_name}\'] => #{attribute_value} #{type(attribute_value)}") 322 | walk_object("#{object_string}[\'#{attribute_name}\']", attribute_value) 323 | elsif attribute_value.method_missing(:class).to_s == 'Array' 324 | attr_info = ping_attr(this_object, attribute_name) 325 | attribute_elements = [] 326 | attribute_value.each do |attribute_element| 327 | attribute_elements << "#{attribute_element}" 328 | end 329 | print_line($recursion_level, 330 | "#{object_string}#{attr_info[:format_string]} = " \ 331 | "#{attribute_elements} (type: Array of Service Models)") 332 | else 333 | print_line($recursion_level, 334 | "*** unhandled attribute type: attribute_value.method_missing(:class) = " \ 335 | "#{attribute_value.method_missing(:class)} ***") 336 | end 337 | else 338 | begin 339 | attr_info = ping_attr(this_object, attribute_name) 340 | if attr_info[:value].nil? 341 | print_line($recursion_level, 342 | "#{object_string}#{attr_info[:format_string]} = nil") if $print_nil_values 343 | else 344 | print_line($recursion_level, 345 | "#{object_string}#{attr_info[:format_string]} = #{attr_info[:value]} #{type(attr_info[:value])}") 346 | end 347 | rescue ArgumentError 348 | if attribute_value.nil? 349 | print_line($recursion_level, 350 | "#{object_string}.#{attribute_name} = nil") if $print_nil_values 351 | else 352 | print_line($recursion_level, 353 | "#{object_string}.#{attribute_name} = #{attribute_value} #{type(attribute_value)}") 354 | end 355 | end 356 | end 357 | else 358 | # 359 | # Option key names can be mixed symbols and strings which confuses .sort 360 | # Create an option_map hash that maps option_name.to_s => option_name 361 | # 362 | option_map = {} 363 | options = attribute_value.keys 364 | options.each do |option_name| 365 | option_map[option_name.to_s] = option_name 366 | end 367 | option_map.keys.sort.each do |option| 368 | if attribute_value[option_map[option]].nil? 369 | print_line($recursion_level, 370 | "#{object_string}.options[#{str_or_sym(option_map[option])}] = nil") if $print_nil_values 371 | else 372 | print_line($recursion_level, 373 | "#{object_string}.options[#{str_or_sym(option_map[option])}] = " \ 374 | "#{attribute_value[option_map[option]]} #{type(attribute_value[option_map[option]])}") 375 | end 376 | end 377 | end 378 | end 379 | print_line($recursion_level, "--- end of attributes ---") 380 | else 381 | print_line($recursion_level, "--- no attributes ---") 382 | end 383 | else 384 | print_line($recursion_level, "*** attributes is not a hash ***") 385 | end 386 | else 387 | print_line($recursion_level, "--- no attributes ---") 388 | end 389 | rescue => err 390 | $evm.log("error", "#{$method} (print_attributes) - [#{err}]\n#{err.backtrace.join("\n")}") 391 | end 392 | end 393 | 394 | def self.print_virtual_columns(object_string, this_object, this_object_class) 395 | begin 396 | # 397 | # Only dump the virtual columns of an MiqAeMethodService::* class 398 | # 399 | if this_object.method_missing(:class).to_s =~ /^MiqAeMethodService.*/ 400 | # 401 | # Print the virtual columns of this object 402 | # 403 | virtual_column_names = [] 404 | if this_object.respond_to?(:virtual_column_names) 405 | virtual_column_names = Array.wrap(this_object.virtual_column_names) 406 | if virtual_column_names.length.zero? 407 | print_line($recursion_level, "--- no virtual columns ---") 408 | else 409 | print_line($recursion_level, "--- virtual columns follow ---") 410 | virtual_column_names.sort.each do |virtual_column_name| 411 | begin 412 | virtual_column_value = this_object.method_missing(:send, virtual_column_name) 413 | if virtual_column_value.nil? 414 | print_line($recursion_level, 415 | "#{object_string}.#{virtual_column_name} = nil") if $print_nil_values 416 | else 417 | print_line($recursion_level, 418 | "#{object_string}.#{virtual_column_name} = " \ 419 | "#{virtual_column_value} #{type(virtual_column_value)}") 420 | end 421 | rescue => err 422 | print_line($recursion_level, 423 | "!!! #{this_object_class} virtual column \'#{virtual_column_name}\' " \ 424 | "throws a #{err.class} exception when accessed (product bug?) !!!") 425 | end 426 | end 427 | print_line($recursion_level, "--- end of virtual columns ---") 428 | end 429 | else 430 | print_line($recursion_level, "--- no virtual columns ---") 431 | end 432 | end 433 | rescue => err 434 | $evm.log("error", "#{$method} (print_virtual_columns) - [#{err}]\n#{err.backtrace.join("\n")}") 435 | end 436 | end 437 | 438 | def self.is_plural?(astring) 439 | astring.singularize != astring 440 | end 441 | 442 | def self.walk_association(object_string, association, associated_objects) 443 | begin 444 | # 445 | # Assemble some fake code to make it look like we're iterating though associations (plural) 446 | # 447 | number_of_associated_objects = associated_objects.length 448 | if is_plural?(association) 449 | assignment_string = "#{object_string}.#{association}.each do |#{association.singularize}|" 450 | else 451 | assignment_string = "#{association} = #{object_string}.#{association}" 452 | end 453 | print_line($recursion_level, "#{assignment_string}") 454 | associated_objects.each do |associated_object| 455 | associated_object_class = "#{associated_object.method_missing(:class)}".demodulize 456 | associated_object_id = associated_object.id rescue associated_object.object_id 457 | print_line($recursion_level, "(object type: #{associated_object_class}, object ID: #{associated_object_id})") 458 | if is_plural?(association) 459 | walk_object("#{association.singularize}", associated_object) 460 | if number_of_associated_objects > 1 461 | print_line($recursion_level, 462 | "--- next #{association.singularize} ---") 463 | number_of_associated_objects -= 1 464 | else 465 | print_line($recursion_level, 466 | "--- end of #{object_string}.#{association}.each do |#{association.singularize}| ---") 467 | end 468 | else 469 | walk_object("#{association}", associated_object) 470 | end 471 | end 472 | rescue => err 473 | $evm.log("error", "#{$method} (walk_association) - [#{err}]\n#{err.backtrace.join("\n")}") 474 | end 475 | end 476 | 477 | def self.print_associations(object_string, this_object, this_object_class) 478 | begin 479 | # 480 | # Only dump the associations of an MiqAeMethodService::* class 481 | # 482 | if this_object.method_missing(:class).to_s =~ /^MiqAeMethodService.*/ 483 | # 484 | # Print the associations of this object according to the 485 | # $walk_associations_whitelist & $walk_associations_blacklist hashes 486 | # 487 | associations = [] 488 | associated_objects = [] 489 | duplicates = [] 490 | if this_object.respond_to?(:associations) 491 | associations = Array.wrap(this_object.associations) 492 | if associations.length.zero? 493 | print_line($recursion_level, "--- no associations ---") 494 | else 495 | print_line($recursion_level, "--- associations follow ---") 496 | duplicates = associations.select{|item| associations.count(item) > 1} 497 | if duplicates.length > 0 498 | print_line($recursion_level, 499 | "*** De-duplicating the following associations: #{duplicates.inspect} ***") 500 | end 501 | associations.uniq.sort.each do |association| 502 | begin 503 | associated_objects = Array.wrap(this_object.method_missing(:send, association)) 504 | if associated_objects.length == 0 505 | print_line($recursion_level, 506 | "#{object_string}.#{association} (type: Association (empty))") 507 | else 508 | print_line($recursion_level, "#{object_string}.#{association} (type: Association)") 509 | # 510 | # See if we need to walk this association according to the walk_association_policy 511 | # variable, and the walk_association_{whitelist,blacklist} hashes 512 | # 513 | if $walk_association_policy == 'whitelist' 514 | if $walk_association_whitelist.has_key?(this_object_class) && 515 | ($walk_association_whitelist[this_object_class].include?('ALL') || 516 | $walk_association_whitelist[this_object_class].include?(association.to_s)) 517 | walk_association(object_string, association, associated_objects) 518 | else 519 | print_line($recursion_level, 520 | "*** not walking: \'#{association}\' isn't in the walk_association_whitelist " \ 521 | "hash for #{this_object_class} ***") 522 | end 523 | elsif $walk_association_policy == 'blacklist' 524 | if $walk_association_blacklist.has_key?(this_object_class) && 525 | ($walk_association_blacklist[this_object_class].include?('ALL') || 526 | $walk_association_blacklist[this_object_class].include?(association.to_s)) 527 | print_line($recursion_level, 528 | "*** not walking: \'#{association}\' is in the walk_association_blacklist " \ 529 | "hash for #{this_object_class} ***") 530 | else 531 | walk_association(object_string, association, associated_objects) 532 | end 533 | else 534 | print_line($recursion_level, 535 | "*** Invalid $walk_association_policy: #{$walk_association_policy} ***") 536 | exit MIQ_ABORT 537 | end 538 | end 539 | rescue => err 540 | print_line($recursion_level, 541 | "!!! #{this_object_class} association \'#{association}\' throws a " \ 542 | "#{err.class} exception when accessed (product bug?) !!!") 543 | next 544 | end 545 | end 546 | print_line($recursion_level, "--- end of associations ---") 547 | end 548 | else 549 | print_line($recursion_level, "--- no associations ---") 550 | end 551 | end 552 | rescue => err 553 | $evm.log("error", "#{$method} (print_associations) - [#{err}]\n#{err.backtrace.join("\n")}") 554 | end 555 | end 556 | 557 | def self.print_methods(object_string, this_object) 558 | begin 559 | # 560 | # Only dump the methods of an MiqAeMethodService::* class 561 | # 562 | if this_object.method_missing(:class).to_s =~ /^MiqAeMethodService.*/ 563 | print_line($recursion_level, 564 | "Class of remote DRb::DRbObject is: #{this_object.method_missing(:class)}") if $debug 565 | # 566 | # Get the instance methods of the class and convert to string 567 | # 568 | if this_object.method_missing(:class).respond_to?(:instance_methods) 569 | instance_methods = this_object.method_missing(:class).instance_methods.map { |x| x.to_s } 570 | # 571 | # Now we need to remove method names that we're not interested in... 572 | # 573 | # ...attribute names... 574 | # 575 | attributes = [] 576 | if this_object.respond_to?(:attributes) 577 | if this_object.attributes.respond_to? :each 578 | this_object.attributes.each do |key, value| 579 | attributes << key 580 | end 581 | end 582 | end 583 | attributes << "attributes" 584 | $evm.log("info", "Removing attributes: #{instance_methods & attributes}") if $debug 585 | instance_methods -= attributes 586 | # 587 | # ...association names... 588 | # 589 | associations = [] 590 | if this_object.respond_to?(:associations) 591 | associations = Array.wrap(this_object.associations) 592 | end 593 | associations << "associations" 594 | $evm.log("info", "Removing associations: #{instance_methods & associations}") if $debug 595 | instance_methods -= associations 596 | # 597 | # ...virtual column names... 598 | # 599 | virtual_column_names = [] 600 | virtual_column_names = this_object.method_missing(:virtual_column_names) 601 | virtual_column_names << "virtual_column_names" 602 | $evm.log("info", "Removing virtual_column_names: #{instance_methods & virtual_column_names}") if $debug 603 | instance_methods -= virtual_column_names 604 | # 605 | # ... MiqAeServiceModelBase methods ... 606 | # 607 | $evm.log("info", "Removing MiqAeServiceModelBase methods: " \ 608 | "#{instance_methods & $service_mode_base_instance_methods}") if $debug 609 | instance_methods -= $service_mode_base_instance_methods 610 | # 611 | # Add in the base methods as it's useful to show that they can be used with this object 612 | # 613 | instance_methods += ['inspect', 'inspect_all', 'reload', 'model_suffix'] 614 | if this_object.respond_to?(:taggable?) 615 | if this_object.taggable? 616 | instance_methods += ['tags', 'tag_assign', 'tag_unassign', 'tagged_with?'] 617 | end 618 | else 619 | instance_methods += ['tags', 'tag_assign', 'tag_unassign', 'tagged_with?'] 620 | end 621 | # 622 | # and finally dump out the list 623 | # 624 | if instance_methods.length.zero? 625 | print_line($recursion_level, "--- no methods ---") 626 | else 627 | print_line($recursion_level, "--- methods follow ---") 628 | instance_methods.sort.each do |instance_method| 629 | print_line($recursion_level, "#{object_string}.#{instance_method}") 630 | end 631 | print_line($recursion_level, "--- end of methods ---") 632 | end 633 | else 634 | print_line($recursion_level, "--- no methods ---") 635 | end 636 | end 637 | rescue => err 638 | $evm.log("error", "#{$method} (print_methods) - [#{err}]\n#{err.backtrace.join("\n")}") 639 | end 640 | end 641 | 642 | def self.print_tags(this_object, this_object_class) 643 | begin 644 | if this_object.respond_to?(:taggable?) 645 | if this_object.taggable? 646 | tags = Array.wrap(this_object.tags) 647 | if tags.length.zero? 648 | print_line($recursion_level, "--- no tags ---") 649 | else 650 | print_line($recursion_level, "--- tags follow ---") 651 | tags.sort.each do |tag| 652 | print_line($recursion_level, "#{tag}") 653 | end 654 | print_line($recursion_level, "--- end of tags ---") 655 | end 656 | else 657 | print_line($recursion_level, "--- object is not taggable ---") 658 | end 659 | else 660 | print_line($recursion_level, "--- no tags, or object is not taggable ---") 661 | end 662 | 663 | rescue NoMethodError 664 | print_line($recursion_level, 665 | "*** #{this_object_class} gives a NoMethodError when the :tags method is accessed (product bug?) ***") 666 | rescue => err 667 | $evm.log("error", "#{$method} (print_tags) - [#{err}]\n#{err.backtrace.join("\n")}") 668 | end 669 | end 670 | 671 | def self.print_custom_attributes(object_string, this_object) 672 | begin 673 | if this_object.respond_to?(:custom_keys) 674 | custom_attribute_keys = Array.wrap(this_object.custom_keys) 675 | if custom_attribute_keys.length.zero? 676 | print_line($recursion_level, "--- no custom attributes ---") 677 | else 678 | print_line($recursion_level, "--- custom attributes follow ---") 679 | custom_attribute_keys.sort.each do |custom_attribute_key| 680 | custom_attribute_value = this_object.custom_get(custom_attribute_key) 681 | print_line($recursion_level, "#{object_string}.custom_get(\'#{custom_attribute_key}\') = \'#{custom_attribute_value}\'") 682 | end 683 | print_line($recursion_level, "--- end of custom attributes ---") 684 | end 685 | else 686 | print_line($recursion_level, "--- object does not support custom attributes ---") 687 | end 688 | rescue => err 689 | $evm.log("error", "#{$method} (print_custom_attributes) - [#{err}]\n#{err.backtrace.join("\n")}") 690 | end 691 | end 692 | 693 | def self.walk_object(object_string, this_object) 694 | begin 695 | # 696 | # Make sure that we don't exceed our maximum recursion level 697 | # 698 | $recursion_level += 1 699 | if $recursion_level > $max_recursion_level 700 | print_line($recursion_level, "*** exceeded maximum recursion level ***") 701 | $recursion_level -= 1 702 | return 703 | end 704 | # 705 | # Make sure we haven't dumped this object already (some data structure links are cyclical) 706 | # 707 | this_object_id = this_object.id.to_s rescue this_object.object_id.to_s 708 | print_line($recursion_level, 709 | "Debug: this_object.method_missing(:class) = #{this_object.method_missing(:class)}") if $debug 710 | this_object_class = "#{this_object.method_missing(:class)}".demodulize 711 | print_line($recursion_level, "Debug: this_object_class = #{this_object_class}") if $debug 712 | if $object_recorder.key?(this_object_class) 713 | if $object_recorder[this_object_class].include?(this_object_id) 714 | print_line($recursion_level, 715 | "Object #{this_object_class} with ID #{this_object_id} has already been printed...") 716 | $recursion_level -= 1 717 | return 718 | else 719 | $object_recorder[this_object_class] << this_object_id 720 | end 721 | else 722 | $object_recorder[this_object_class] = [] 723 | $object_recorder[this_object_class] << this_object_id 724 | end 725 | # 726 | # Dump out the things of interest 727 | # 728 | print_attributes(object_string, this_object) 729 | print_virtual_columns(object_string, this_object, this_object_class) 730 | print_associations(object_string, this_object, this_object_class) 731 | print_methods(object_string, this_object) if $print_methods 732 | print_tags(this_object, this_object_class) if $service_model_base_supports_taggable 733 | print_custom_attributes(object_string, this_object) 734 | 735 | $recursion_level -= 1 736 | rescue => err 737 | $evm.log("error", "#{$method} (walk_object) - [#{err}]\n#{err.backtrace.join("\n")}") 738 | $recursion_level -= 1 739 | end 740 | end 741 | end 742 | end 743 | end 744 | if $evm.object.name.match("ObjectWalker/object_walker") 745 | Investigative_Debugging::Discovery::ObjectWalker.walk_objects 746 | end -------------------------------------------------------------------------------- /Investigative_Debugging/Discovery/ObjectWalker.class/__methods__/object_walker.rb: -------------------------------------------------------------------------------- 1 | module Investigative_Debugging 2 | module Discovery 3 | class ObjectWalker 4 | # 5 | # Can be called from anywhere in the CloudForms/ManageIQ automation namespace, and will walk the automation object structure starting from $evm.root 6 | # and dump (to automation.log) its attributes, any objects found, their attributes, virtual columns, and associations, and so on. 7 | # 8 | # Author: Peter McGowan (pemcg@redhat.com) 9 | # Copyright 2018 Peter McGowan, Red Hat 10 | # 11 | require 'active_support/core_ext/string' 12 | require 'securerandom' 13 | require 'json' 14 | 15 | VERSION = "2.0" 16 | MAX_RECURSION_LEVEL = 7 17 | $debug = false 18 | $print_methods = true 19 | 20 | def self.walk_objects 21 | begin 22 | $recursion_level = 0 23 | $object_recorder = {} 24 | $service_model_base_supports_taggable = false 25 | # 26 | # Read our configuration instance to get the variables 27 | # 28 | instance = $evm.instantiate('/Discovery/ObjectWalker/configuration') 29 | if instance.nil? 30 | $evm.log(:error, "*** Instance /Discovery/ObjectWalker/configuration not found in Datastore ***") 31 | exit MIQ_ERROR 32 | end 33 | # 34 | # We need to record the instance methods of the MiqAeMethodService::MiqAeServiceModelBase class so that we can 35 | # subtract this list from the methods we discover for each object 36 | # 37 | $service_mode_base_instance_methods = [] 38 | # 39 | # Change $max_recursion_level to adjust the depth of recursion that object_walker traverses through the objects 40 | # 41 | $max_recursion_level = instance['max_recursion_level'] || MAX_RECURSION_LEVEL 42 | # 43 | # $print_evm_object can be used to toggle whether or not to walk the object structure of the $evm.object (i.e current) object 44 | # If object_walker has been $evm.instantiated or invoked from a relationship then $evm.object will be object_walker itself. If 45 | # object_walker has been invoked from an embedded method then $evm.object is the calling method's instance. 46 | # 47 | $print_evm_object = instance['print_evm_object'].nil? ? false : instance['print_evm_object'] 48 | unless [FalseClass, TrueClass].include? $print_evm_object.class 49 | $evm.log(:error, "*** print_evm_object must be a boolean value ***") 50 | exit MIQ_ERROR 51 | end 52 | # 53 | # $print_evm_parent can be used to toggle whether or not to walk the object structure of the $evm.parent object 54 | # If object_walker has been $evm.instantiated or invoked from a relationship then $evm.parent will be the calling 55 | # instance 56 | # 57 | $print_evm_parent = instance['print_evm_parent'].nil? ? false : instance['print_evm_parent'] 58 | unless [FalseClass, TrueClass].include? $print_evm_parent.class 59 | $evm.log(:error, "*** print_evm_parent must be a boolean value ***") 60 | exit MIQ_ERROR 61 | end 62 | # 63 | # $print_nil_values can be used to toggle whether or not to include keys that have a nil value in the 64 | # output dump. There are often many, and including them will usually increase verbosity, but it is 65 | # sometimes useful to know that a key/attribute exists, even if it currently has no assigned value. 66 | # 67 | $print_nil_values = instance['print_nil_values'].nil? ? true : instance['print_nil_values'] 68 | unless [FalseClass, TrueClass].include? $print_nil_values.class 69 | $evm.log(:error, "*** print_nil_values must be a boolean value ***") 70 | exit MIQ_ERROR 71 | end 72 | # 73 | # $walk_association_policy should have the value of either 'whitelist' or 'blacklist'. This will determine whether we either 74 | # walk all associations _except_ those in the walk_association_blacklist hash, or _only_ the associations in the 75 | # walk_association_whitelist hash 76 | # 77 | $walk_association_policy = instance['walk_association_policy'] || 'whitelist' 78 | # 79 | # if $walk_association_policy = 'whitelist', then object_walker will only traverse associations of objects that are explicitly 80 | # mentioned in the $walk_association_whitelist hash. This enables us to carefully control what is dumped. If object_walker finds 81 | # an association that isn't in the hash, it will print a line similar to: 82 | # 83 | # $evm.root['user'].current_tenant (type: Association) 84 | # *** not walking: 'current_tenant' isn't in the walk_association_whitelist hash for MiqAeServiceUser *** 85 | # 86 | # If you wish to explore and dump this association, edit the hash to add the association name to the list associated with the object type. The string 87 | # 'ALL' can be used to walk all associations of an object type 88 | # 89 | dialog_walk_association_whitelist = ($evm.root['dialog_walk_association_whitelist'] != '') ? $evm.root['dialog_walk_association_whitelist'] : nil 90 | walk_association_whitelist = dialog_walk_association_whitelist || instance['walk_association_whitelist'] 91 | # 92 | # if $walk_association_policy = 'blacklist', then object_walker will traverse all associations of all objects, except those 93 | # that are explicitly mentioned in the $walk_association_blacklist hash. This enables us to run a more exploratory dump, at the cost of a 94 | # much more verbose output. The string 'ALL' can be used to prevent walking any associations of an object type 95 | # 96 | # You have been warned, using a blacklist walk_association_policy produces a lot of output! 97 | # 98 | dialog_walk_association_blacklist = ($evm.root['dialog_walk_association_blacklist'] != '') ? $evm.root['dialog_walk_association_blacklist'] : nil 99 | walk_association_blacklist = dialog_walk_association_blacklist || instance['walk_association_blacklist'] 100 | # 101 | # Generate a random string to identify this object_walker dump 102 | # 103 | randomstring = SecureRandom.hex(4).upcase 104 | $method = "object_walker##{randomstring}" 105 | 106 | $evm.log("info", "#{$method}: Object Walker #{VERSION} Starting") 107 | print_line(0, "*** detected 'print_nil_values = false' so attributes with nil values will not be printed ***") if !$print_nil_values 108 | # 109 | # If we're dumping object methods, then we need to find out the methods of the 110 | # MiqAeMethodService::MiqAeServiceModelBase class so that we can subtract them from the method list 111 | # returned from each object. We know that MiqAeServiceModelBase is the superclass of 112 | # MiqAeMethodService::MiqAeServiceMiqServer, so we can get what we're after via $evm.root['miq_server'] 113 | # 114 | miq_server = $evm.root['miq_server'] rescue nil 115 | unless miq_server.nil? 116 | if miq_server.method_missing(:class).superclass.name == "MiqAeMethodService::MiqAeServiceModelBase" 117 | $service_mode_base_instance_methods = miq_server.method_missing(:class).superclass.instance_methods.map { |x| x.to_s } 118 | else 119 | $evm.log("error", "#{$method} Unexpected parent class of $evm.root['miq_server']: " \ 120 | "#{miq_server.method_missing(:class).superclass.name}") 121 | $print_methods = false 122 | end 123 | else 124 | $evm.log("error", "#{$method} $evm.root['miq_server'] doesn't exist") 125 | $print_methods = false 126 | end 127 | $service_model_base_supports_taggable = true if $service_mode_base_instance_methods.include?('taggable?') 128 | 129 | print_line(0, "--- walk_association_policy details ---") 130 | print_line(0, "walk_association_policy = #{$walk_association_policy}") 131 | case $walk_association_policy 132 | when 'whitelist' 133 | if walk_association_whitelist.nil? 134 | $evm.log(:error, "*** walk_association_whitelist not found, please define one as an instance attribute or a dialog variable ***") 135 | exit MIQ_ERROR 136 | else 137 | $walk_association_whitelist = JSON.parse(walk_association_whitelist.gsub(/\s/,'').gsub(/(? err 217 | $evm.log("error", "#{$method} (object_walker) - Invalid JSON string passed as #{$walk_association_policy}") 218 | $evm.log("error", "#{$method} (object_walker) - Err: #{err.inspect}") 219 | exit MIQ_ERROR 220 | rescue => err 221 | $evm.log("error", "#{$method} (object_walker) - [#{err}]\n#{err.backtrace.join("\n")}") 222 | exit MIQ_ERROR 223 | end 224 | end 225 | 226 | private 227 | 228 | def self.walk_automation_objects(service_object) 229 | automation_object = Struct::ServiceObject.new(service_object.to_s, "", Array.new) 230 | if service_object.to_s == $evm.root.to_s 231 | automation_object.position = 'root' 232 | elsif service_object.to_s == $evm.parent.to_s 233 | automation_object.position = 'parent' 234 | elsif service_object.to_s == $evm.object.to_s 235 | automation_object.position = 'object' 236 | end 237 | offspring = service_object.children 238 | unless offspring.nil? || (offspring.kind_of?(Array) and offspring.length.zero?) 239 | Array.wrap(offspring).each do |child| 240 | automation_object.children << walk_automation_objects(child) 241 | end 242 | end 243 | return automation_object 244 | end 245 | 246 | def self.print_automation_objects(indent_level, hierarchy) 247 | case hierarchy.position 248 | when 'root' 249 | print_line(indent_level, "#{hierarchy.obj_name} ($evm.root)") 250 | when 'parent' 251 | print_line(indent_level, "#{hierarchy.obj_name} ($evm.parent)") 252 | when 'object' 253 | print_line(indent_level, "#{hierarchy.obj_name} ($evm.object)") 254 | else 255 | print_line(indent_level, "#{hierarchy.obj_name}") 256 | end 257 | indent_level += 1 258 | hierarchy.children.each do |child| 259 | print_automation_objects(indent_level, child) 260 | end 261 | end 262 | 263 | def self.print_line(indent_level, string) 264 | $evm.log("info", "#{$method}:[#{indent_level.to_s}] #{string}") 265 | end 266 | 267 | def self.type(object) 268 | if object.is_a?(DRb::DRbObject) 269 | string = "(type: #{object.class}, URI: #{object.__drburi()})" 270 | else 271 | string = "(type: #{object.class})" 272 | end 273 | return string 274 | end 275 | 276 | def self.ping_attr(this_object, attribute) 277 | value = "" 278 | format_string = "." 279 | begin 280 | # 281 | # See if it's an attribute that we access using '.attribute' 282 | # 283 | value = this_object.method_missing(:send, attribute) 284 | format_string = ".#{attribute}" 285 | rescue NoMethodError 286 | # 287 | # Seems not, let's try to access as if it's a hash value 288 | # 289 | value = this_object[attribute] 290 | format_string = "['#{attribute}']" 291 | end 292 | return {:format_string => format_string, :value => value} 293 | end 294 | 295 | def self.str_or_sym(value) 296 | value_as_string = "" 297 | if value.is_a?(Symbol) 298 | value_as_string = ":#{value}" 299 | else 300 | value_as_string = "\'#{value}\'" 301 | end 302 | return value_as_string 303 | end 304 | 305 | def self.print_attributes(object_string, this_object) 306 | begin 307 | # 308 | # Print the attributes of this object 309 | # 310 | if this_object.respond_to?(:attributes) 311 | print_line($recursion_level, "Debug: this_object.inspected = #{this_object.inspect}") if $debug 312 | if this_object.attributes.respond_to?(:keys) 313 | if this_object.attributes.keys.length > 0 314 | print_line($recursion_level, "--- attributes follow ---") 315 | this_object.attributes.keys.sort.each do |attribute_name| 316 | attribute_value = this_object.attributes[attribute_name] 317 | if attribute_name != "options" 318 | if attribute_value.is_a?(DRb::DRbObject) 319 | if attribute_value.method_missing(:class).to_s =~ /^MiqAeMethodService.*/ 320 | print_line($recursion_level, 321 | "#{object_string}[\'#{attribute_name}\'] => #{attribute_value} #{type(attribute_value)}") 322 | walk_object("#{object_string}[\'#{attribute_name}\']", attribute_value) 323 | elsif attribute_value.method_missing(:class).to_s == 'Array' 324 | attr_info = ping_attr(this_object, attribute_name) 325 | attribute_elements = [] 326 | attribute_value.each do |attribute_element| 327 | attribute_elements << "#{attribute_element}" 328 | end 329 | print_line($recursion_level, 330 | "#{object_string}#{attr_info[:format_string]} = " \ 331 | "#{attribute_elements} (type: Array of Service Models)") 332 | else 333 | print_line($recursion_level, 334 | "*** unhandled attribute type: attribute_value.method_missing(:class) = " \ 335 | "#{attribute_value.method_missing(:class)} ***") 336 | end 337 | else 338 | begin 339 | attr_info = ping_attr(this_object, attribute_name) 340 | if attr_info[:value].nil? 341 | print_line($recursion_level, 342 | "#{object_string}#{attr_info[:format_string]} = nil") if $print_nil_values 343 | else 344 | print_line($recursion_level, 345 | "#{object_string}#{attr_info[:format_string]} = #{attr_info[:value]} #{type(attr_info[:value])}") 346 | end 347 | rescue ArgumentError 348 | if attribute_value.nil? 349 | print_line($recursion_level, 350 | "#{object_string}.#{attribute_name} = nil") if $print_nil_values 351 | else 352 | print_line($recursion_level, 353 | "#{object_string}.#{attribute_name} = #{attribute_value} #{type(attribute_value)}") 354 | end 355 | end 356 | end 357 | else 358 | # 359 | # Option key names can be mixed symbols and strings which confuses .sort 360 | # Create an option_map hash that maps option_name.to_s => option_name 361 | # 362 | option_map = {} 363 | options = attribute_value.keys 364 | options.each do |option_name| 365 | option_map[option_name.to_s] = option_name 366 | end 367 | option_map.keys.sort.each do |option| 368 | if attribute_value[option_map[option]].nil? 369 | print_line($recursion_level, 370 | "#{object_string}.options[#{str_or_sym(option_map[option])}] = nil") if $print_nil_values 371 | else 372 | print_line($recursion_level, 373 | "#{object_string}.options[#{str_or_sym(option_map[option])}] = " \ 374 | "#{attribute_value[option_map[option]]} #{type(attribute_value[option_map[option]])}") 375 | end 376 | end 377 | end 378 | end 379 | print_line($recursion_level, "--- end of attributes ---") 380 | else 381 | print_line($recursion_level, "--- no attributes ---") 382 | end 383 | else 384 | print_line($recursion_level, "*** attributes is not a hash ***") 385 | end 386 | else 387 | print_line($recursion_level, "--- no attributes ---") 388 | end 389 | rescue => err 390 | $evm.log("error", "#{$method} (print_attributes) - [#{err}]\n#{err.backtrace.join("\n")}") 391 | end 392 | end 393 | 394 | def self.print_virtual_columns(object_string, this_object, this_object_class) 395 | begin 396 | # 397 | # Only dump the virtual columns of an MiqAeMethodService::* class 398 | # 399 | if this_object.method_missing(:class).to_s =~ /^MiqAeMethodService.*/ 400 | # 401 | # Print the virtual columns of this object 402 | # 403 | virtual_column_names = [] 404 | if this_object.respond_to?(:virtual_column_names) 405 | virtual_column_names = Array.wrap(this_object.virtual_column_names) 406 | if virtual_column_names.length.zero? 407 | print_line($recursion_level, "--- no virtual columns ---") 408 | else 409 | print_line($recursion_level, "--- virtual columns follow ---") 410 | virtual_column_names.sort.each do |virtual_column_name| 411 | begin 412 | virtual_column_value = this_object.method_missing(:send, virtual_column_name) 413 | if virtual_column_value.nil? 414 | print_line($recursion_level, 415 | "#{object_string}.#{virtual_column_name} = nil") if $print_nil_values 416 | else 417 | print_line($recursion_level, 418 | "#{object_string}.#{virtual_column_name} = " \ 419 | "#{virtual_column_value} #{type(virtual_column_value)}") 420 | end 421 | rescue => err 422 | print_line($recursion_level, 423 | "!!! #{this_object_class} virtual column \'#{virtual_column_name}\' " \ 424 | "throws a #{err.class} exception when accessed (product bug?) !!!") 425 | end 426 | end 427 | print_line($recursion_level, "--- end of virtual columns ---") 428 | end 429 | else 430 | print_line($recursion_level, "--- no virtual columns ---") 431 | end 432 | end 433 | rescue => err 434 | $evm.log("error", "#{$method} (print_virtual_columns) - [#{err}]\n#{err.backtrace.join("\n")}") 435 | end 436 | end 437 | 438 | def self.is_plural?(astring) 439 | astring.singularize != astring 440 | end 441 | 442 | def self.walk_association(object_string, association, associated_objects) 443 | begin 444 | # 445 | # Assemble some fake code to make it look like we're iterating though associations (plural) 446 | # 447 | number_of_associated_objects = associated_objects.length 448 | if is_plural?(association) 449 | assignment_string = "#{object_string}.#{association}.each do |#{association.singularize}|" 450 | else 451 | assignment_string = "#{association} = #{object_string}.#{association}" 452 | end 453 | print_line($recursion_level, "#{assignment_string}") 454 | associated_objects.each do |associated_object| 455 | associated_object_class = "#{associated_object.method_missing(:class)}".demodulize 456 | associated_object_id = associated_object.id rescue associated_object.object_id 457 | print_line($recursion_level, "(object type: #{associated_object_class}, object ID: #{associated_object_id})") 458 | if is_plural?(association) 459 | walk_object("#{association.singularize}", associated_object) 460 | if number_of_associated_objects > 1 461 | print_line($recursion_level, 462 | "--- next #{association.singularize} ---") 463 | number_of_associated_objects -= 1 464 | else 465 | print_line($recursion_level, 466 | "--- end of #{object_string}.#{association}.each do |#{association.singularize}| ---") 467 | end 468 | else 469 | walk_object("#{association}", associated_object) 470 | end 471 | end 472 | rescue => err 473 | $evm.log("error", "#{$method} (walk_association) - [#{err}]\n#{err.backtrace.join("\n")}") 474 | end 475 | end 476 | 477 | def self.print_associations(object_string, this_object, this_object_class) 478 | begin 479 | # 480 | # Only dump the associations of an MiqAeMethodService::* class 481 | # 482 | if this_object.method_missing(:class).to_s =~ /^MiqAeMethodService.*/ 483 | # 484 | # Print the associations of this object according to the 485 | # $walk_associations_whitelist & $walk_associations_blacklist hashes 486 | # 487 | associations = [] 488 | associated_objects = [] 489 | duplicates = [] 490 | if this_object.respond_to?(:associations) 491 | associations = Array.wrap(this_object.associations) 492 | if associations.length.zero? 493 | print_line($recursion_level, "--- no associations ---") 494 | else 495 | print_line($recursion_level, "--- associations follow ---") 496 | duplicates = associations.select{|item| associations.count(item) > 1} 497 | if duplicates.length > 0 498 | print_line($recursion_level, 499 | "*** De-duplicating the following associations: #{duplicates.inspect} ***") 500 | end 501 | associations.uniq.sort.each do |association| 502 | begin 503 | associated_objects = Array.wrap(this_object.method_missing(:send, association)) 504 | if associated_objects.length == 0 505 | print_line($recursion_level, 506 | "#{object_string}.#{association} (type: Association (empty))") 507 | else 508 | print_line($recursion_level, "#{object_string}.#{association} (type: Association)") 509 | # 510 | # See if we need to walk this association according to the walk_association_policy 511 | # variable, and the walk_association_{whitelist,blacklist} hashes 512 | # 513 | if $walk_association_policy == 'whitelist' 514 | if $walk_association_whitelist.has_key?(this_object_class) && 515 | ($walk_association_whitelist[this_object_class].include?('ALL') || 516 | $walk_association_whitelist[this_object_class].include?(association.to_s)) 517 | walk_association(object_string, association, associated_objects) 518 | else 519 | print_line($recursion_level, 520 | "*** not walking: \'#{association}\' isn't in the walk_association_whitelist " \ 521 | "hash for #{this_object_class} ***") 522 | end 523 | elsif $walk_association_policy == 'blacklist' 524 | if $walk_association_blacklist.has_key?(this_object_class) && 525 | ($walk_association_blacklist[this_object_class].include?('ALL') || 526 | $walk_association_blacklist[this_object_class].include?(association.to_s)) 527 | print_line($recursion_level, 528 | "*** not walking: \'#{association}\' is in the walk_association_blacklist " \ 529 | "hash for #{this_object_class} ***") 530 | else 531 | walk_association(object_string, association, associated_objects) 532 | end 533 | else 534 | print_line($recursion_level, 535 | "*** Invalid $walk_association_policy: #{$walk_association_policy} ***") 536 | exit MIQ_ABORT 537 | end 538 | end 539 | rescue => err 540 | print_line($recursion_level, 541 | "!!! #{this_object_class} association \'#{association}\' throws a " \ 542 | "#{err.class} exception when accessed (product bug?) !!!") 543 | next 544 | end 545 | end 546 | print_line($recursion_level, "--- end of associations ---") 547 | end 548 | else 549 | print_line($recursion_level, "--- no associations ---") 550 | end 551 | end 552 | rescue => err 553 | $evm.log("error", "#{$method} (print_associations) - [#{err}]\n#{err.backtrace.join("\n")}") 554 | end 555 | end 556 | 557 | def self.print_methods(object_string, this_object) 558 | begin 559 | # 560 | # Only dump the methods of an MiqAeMethodService::* class 561 | # 562 | if this_object.method_missing(:class).to_s =~ /^MiqAeMethodService.*/ 563 | print_line($recursion_level, 564 | "Class of remote DRb::DRbObject is: #{this_object.method_missing(:class)}") if $debug 565 | # 566 | # Get the instance methods of the class and convert to string 567 | # 568 | if this_object.method_missing(:class).respond_to?(:instance_methods) 569 | instance_methods = this_object.method_missing(:class).instance_methods.map { |x| x.to_s } 570 | # 571 | # Now we need to remove method names that we're not interested in... 572 | # 573 | # ...attribute names... 574 | # 575 | attributes = [] 576 | if this_object.respond_to?(:attributes) 577 | if this_object.attributes.respond_to? :each 578 | this_object.attributes.each do |key, value| 579 | attributes << key 580 | end 581 | end 582 | end 583 | attributes << "attributes" 584 | $evm.log("info", "Removing attributes: #{instance_methods & attributes}") if $debug 585 | instance_methods -= attributes 586 | # 587 | # ...association names... 588 | # 589 | associations = [] 590 | if this_object.respond_to?(:associations) 591 | associations = Array.wrap(this_object.associations) 592 | end 593 | associations << "associations" 594 | $evm.log("info", "Removing associations: #{instance_methods & associations}") if $debug 595 | instance_methods -= associations 596 | # 597 | # ...virtual column names... 598 | # 599 | virtual_column_names = [] 600 | virtual_column_names = this_object.method_missing(:virtual_column_names) 601 | virtual_column_names << "virtual_column_names" 602 | $evm.log("info", "Removing virtual_column_names: #{instance_methods & virtual_column_names}") if $debug 603 | instance_methods -= virtual_column_names 604 | # 605 | # ... MiqAeServiceModelBase methods ... 606 | # 607 | $evm.log("info", "Removing MiqAeServiceModelBase methods: " \ 608 | "#{instance_methods & $service_mode_base_instance_methods}") if $debug 609 | instance_methods -= $service_mode_base_instance_methods 610 | # 611 | # Add in the base methods as it's useful to show that they can be used with this object 612 | # 613 | instance_methods += ['inspect', 'inspect_all', 'reload', 'model_suffix'] 614 | if this_object.respond_to?(:taggable?) 615 | if this_object.taggable? 616 | instance_methods += ['tags', 'tag_assign', 'tag_unassign', 'tagged_with?'] 617 | end 618 | else 619 | instance_methods += ['tags', 'tag_assign', 'tag_unassign', 'tagged_with?'] 620 | end 621 | # 622 | # and finally dump out the list 623 | # 624 | if instance_methods.length.zero? 625 | print_line($recursion_level, "--- no methods ---") 626 | else 627 | print_line($recursion_level, "--- methods follow ---") 628 | instance_methods.sort.each do |instance_method| 629 | print_line($recursion_level, "#{object_string}.#{instance_method}") 630 | end 631 | print_line($recursion_level, "--- end of methods ---") 632 | end 633 | else 634 | print_line($recursion_level, "--- no methods ---") 635 | end 636 | end 637 | rescue => err 638 | $evm.log("error", "#{$method} (print_methods) - [#{err}]\n#{err.backtrace.join("\n")}") 639 | end 640 | end 641 | 642 | def self.print_tags(this_object, this_object_class) 643 | begin 644 | if this_object.respond_to?(:taggable?) 645 | if this_object.taggable? 646 | tags = Array.wrap(this_object.tags) 647 | if tags.length.zero? 648 | print_line($recursion_level, "--- no tags ---") 649 | else 650 | print_line($recursion_level, "--- tags follow ---") 651 | tags.sort.each do |tag| 652 | print_line($recursion_level, "#{tag}") 653 | end 654 | print_line($recursion_level, "--- end of tags ---") 655 | end 656 | else 657 | print_line($recursion_level, "--- object is not taggable ---") 658 | end 659 | else 660 | print_line($recursion_level, "--- no tags, or object is not taggable ---") 661 | end 662 | 663 | rescue NoMethodError 664 | print_line($recursion_level, 665 | "*** #{this_object_class} gives a NoMethodError when the :tags method is accessed (product bug?) ***") 666 | rescue => err 667 | $evm.log("error", "#{$method} (print_tags) - [#{err}]\n#{err.backtrace.join("\n")}") 668 | end 669 | end 670 | 671 | def self.print_custom_attributes(object_string, this_object) 672 | begin 673 | if this_object.respond_to?(:custom_keys) 674 | custom_attribute_keys = Array.wrap(this_object.custom_keys) 675 | if custom_attribute_keys.length.zero? 676 | print_line($recursion_level, "--- no custom attributes ---") 677 | else 678 | print_line($recursion_level, "--- custom attributes follow ---") 679 | custom_attribute_keys.sort.each do |custom_attribute_key| 680 | custom_attribute_value = this_object.custom_get(custom_attribute_key) 681 | print_line($recursion_level, "#{object_string}.custom_get(\'#{custom_attribute_key}\') = \'#{custom_attribute_value}\'") 682 | end 683 | print_line($recursion_level, "--- end of custom attributes ---") 684 | end 685 | else 686 | print_line($recursion_level, "--- object does not support custom attributes ---") 687 | end 688 | rescue => err 689 | $evm.log("error", "#{$method} (print_custom_attributes) - [#{err}]\n#{err.backtrace.join("\n")}") 690 | end 691 | end 692 | 693 | def self.walk_object(object_string, this_object) 694 | begin 695 | # 696 | # Make sure that we don't exceed our maximum recursion level 697 | # 698 | $recursion_level += 1 699 | if $recursion_level > $max_recursion_level 700 | print_line($recursion_level, "*** exceeded maximum recursion level ***") 701 | $recursion_level -= 1 702 | return 703 | end 704 | # 705 | # Make sure we haven't dumped this object already (some data structure links are cyclical) 706 | # 707 | this_object_id = this_object.id.to_s rescue this_object.object_id.to_s 708 | print_line($recursion_level, 709 | "Debug: this_object.method_missing(:class) = #{this_object.method_missing(:class)}") if $debug 710 | this_object_class = "#{this_object.method_missing(:class)}".demodulize 711 | print_line($recursion_level, "Debug: this_object_class = #{this_object_class}") if $debug 712 | if $object_recorder.key?(this_object_class) 713 | if $object_recorder[this_object_class].include?(this_object_id) 714 | print_line($recursion_level, 715 | "Object #{this_object_class} with ID #{this_object_id} has already been printed...") 716 | $recursion_level -= 1 717 | return 718 | else 719 | $object_recorder[this_object_class] << this_object_id 720 | end 721 | else 722 | $object_recorder[this_object_class] = [] 723 | $object_recorder[this_object_class] << this_object_id 724 | end 725 | # 726 | # Dump out the things of interest 727 | # 728 | print_attributes(object_string, this_object) 729 | print_virtual_columns(object_string, this_object, this_object_class) 730 | print_associations(object_string, this_object, this_object_class) 731 | print_methods(object_string, this_object) if $print_methods 732 | print_tags(this_object, this_object_class) if $service_model_base_supports_taggable 733 | print_custom_attributes(object_string, this_object) 734 | 735 | $recursion_level -= 1 736 | rescue => err 737 | $evm.log("error", "#{$method} (walk_object) - [#{err}]\n#{err.backtrace.join("\n")}") 738 | $recursion_level -= 1 739 | end 740 | end 741 | end 742 | end 743 | end 744 | if $evm.object.name.match("ObjectWalker/object_walker") 745 | Investigative_Debugging::Discovery::ObjectWalker.walk_objects 746 | end 747 | -------------------------------------------------------------------------------- /object_associations: -------------------------------------------------------------------------------- 1 | 2 | 'MiqAeServiceAccount': ['host', 'vm_or_template'] 3 | 4 | 'MiqAeServiceAutomationRequest': ['automation_tasks', 'destination', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant'] 5 | 6 | 'MiqAeServiceAutomationTask': ['automation_request', 'destination', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant'] 7 | 8 | 'MiqAeServiceAvailabilityZone': ['cloud_subnets', 'ext_management_system', 'vms', 'vms_and_templates'] 9 | 10 | 'MiqAeServiceClassification': ['parent'] 11 | 12 | 'MiqAeServiceCloudNetwork': ['cloud_subnets', 'cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'network_routers', 'security_groups', 'vms'] 13 | 14 | 'MiqAeServiceCloudObjectStoreContainer': ['cloud_object_store_objects', 'cloud_tenant', 'ext_management_system'] 15 | 16 | 'MiqAeServiceCloudObjectStoreObject': ['cloud_object_store_container', 'cloud_tenant', 'ext_management_system'] 17 | 18 | 'MiqAeServiceCloudResourceQuota': ['cloud_tenant', 'ext_management_system'] 19 | 20 | 'MiqAeServiceCloudSubnet': ['availability_zone', 'cloud_network', 'vms'] 21 | 22 | 'MiqAeServiceCloudTenant': ['cloud_networks', 'cloud_resource_quotas', 'ext_management_system', 'floating_ips', 'miq_templates', 'security_groups', 'vms', 'vms_and_templates'] 23 | 24 | 'MiqAeServiceCloudVolume': ['attachments', 'availability_zone', 'base_snapshot', 'cloud_tenant', 'cloud_volume_snapshots', 'ext_management_system'] 25 | 26 | 'MiqAeServiceCloudVolumeSnapshot': ['based_volumes', 'cloud_tenant', 'cloud_volume', 'ext_management_system'] 27 | 28 | 'MiqAeServiceConfigurationArchitecture': ['configuration_profiles', 'configured_systems', 'manager'] 29 | 30 | 'MiqAeServiceConfigurationComputeProfile': ['configuration_profiles', 'configured_systems', 'manager'] 31 | 32 | 'MiqAeServiceConfigurationDomain': ['configuration_profiles', 'configured_systems', 'manager'] 33 | 34 | 'MiqAeServiceConfigurationEnvironment': ['configuration_profiles', 'configured_systems', 'manager'] 35 | 36 | 'MiqAeServiceConfigurationLocation': ['parent', 'provisioning_manager'] 37 | 38 | 'MiqAeServiceConfigurationOrganization': ['parent', 'provisioning_manager'] 39 | 40 | 'MiqAeServiceConfigurationProfile': ['configured_systems', 'customization_script_medium', 'customization_script_ptable', 'manager', 'operating_system_flavors', 'parent'] 41 | 42 | 'MiqAeServiceConfigurationRealm': ['configuration_profiles', 'configured_systems', 'manager'] 43 | 44 | 'MiqAeServiceConfigurationScript': ['inventory_root_group', 'manager'] 45 | 46 | 'MiqAeServiceConfigurationTag': ['configuration_profiles', 'configured_systems', 'manager'] 47 | 48 | 'MiqAeServiceConfiguredSystem': ['computer_system', 'configuration_location', 'configuration_profile', 'customization_script_media', 'customization_script_ptable', 'manager', 'operating_system_flavor'] 49 | 50 | 'MiqAeServiceCustomEvent': ['dest_host', 'dest_vm', 'ems', 'ext_management_system', 'host', 'service', 'src_host', 'src_vm', 'vm'] 51 | 52 | 'MiqAeServiceCustomizationScript': ['provisioning_manager'] 53 | 54 | 'MiqAeServiceCustomizationScriptMedium': ['provisioning_manager'] 55 | 56 | 'MiqAeServiceCustomizationScriptPtable': ['provisioning_manager'] 57 | 58 | 'MiqAeServiceCustomizationSpec': ['ext_management_system'] 59 | 60 | 'MiqAeServiceCustomizationTemplate': ['pxe_images'] 61 | 62 | 'MiqAeServiceCustomizationTemplateCloudInit': ['pxe_images'] 63 | 64 | 'MiqAeServiceCustomizationTemplateKickstart': ['pxe_images'] 65 | 66 | 'MiqAeServiceCustomizationTemplateSysprep': ['pxe_images'] 67 | 68 | 'MiqAeServiceDatacenter': ['hosts', 'vms'] 69 | 70 | 'MiqAeServiceDisk': ['backing', 'hardware', 'storage'] 71 | 72 | 'MiqAeServiceEmsCluster': ['all_resource_pools', 'all_vms', 'default_resource_pool', 'ems_events', 'ext_management_system', 'hosts', 'parent_folder', 'resource_pools', 'storages', 'vms'] 73 | 74 | 'MiqAeServiceEmsEvent': ['dest_host', 'dest_vm', 'ems', 'ext_management_system', 'host', 'service', 'src_host', 'src_vm', 'vm'] 75 | 76 | 'MiqAeServiceEmsFolder': ['hosts', 'vms'] 77 | 78 | 'MiqAeServiceEmsInfra': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 79 | 80 | 'MiqAeServiceEventStream': ['dest_host', 'dest_vm', 'ems', 'ext_management_system', 'host', 'service', 'src_host', 'src_vm', 'vm'] 81 | 82 | 'MiqAeServiceExtManagementSystem': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 83 | 84 | 'MiqAeServiceFirewallRule': ['resource', 'source_security_group'] 85 | 86 | 'MiqAeServiceFlavor': ['ext_management_system', 'vms'] 87 | 88 | 'MiqAeServiceFloatingIp': ['cloud_tenant', 'ext_management_system', 'vm'] 89 | 90 | 'MiqAeServiceGuestApplication': ['host', 'vm'] 91 | 92 | 'MiqAeServiceGuestDevice': ['hardware', 'lan', 'network', 'switch'] 93 | 94 | 'MiqAeServiceHardware': ['guest_devices', 'host', 'networks', 'nics', 'ports', 'storage_adapters', 'vm'] 95 | 96 | 'MiqAeServiceHost': ['datacenter', 'directories', 'ems_cluster', 'ems_events', 'ems_folder', 'ext_management_system', 'files', 'guest_applications', 'hardware', 'lans', 'operating_system', 'storages', 'switches', 'vms'] 97 | 98 | 'MiqAeServiceLan': ['guest_devices', 'hosts', 'switch', 'templates', 'vms'] 99 | 100 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager': ['availability_zones', 'cloud_networks', 'cloud_resource_quotas', 'cloud_tenants', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'flavors', 'floating_ips', 'hosts', 'key_pairs', 'miq_templates', 'network_manager', 'orchestration_stacks', 'private_networks', 'provider', 'public_networks', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 101 | 102 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_AvailabilityZone': ['cloud_subnets', 'ext_management_system', 'vms', 'vms_and_templates'] 103 | 104 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_CloudVolume': ['attachments', 'availability_zone', 'base_snapshot', 'cloud_tenant', 'cloud_volume_snapshots', 'ext_management_system'] 105 | 106 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_CloudVolumeSnapshot': ['based_volumes', 'cloud_tenant', 'cloud_volume', 'ext_management_system'] 107 | 108 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_Flavor': ['ext_management_system', 'vms'] 109 | 110 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_OrchestrationStack': ['cloud_networks', 'ext_management_system', 'orchestration_template', 'outputs', 'parameters', 'resources', 'security_groups', 'vms'] 111 | 112 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_Provision': ['destination', 'eligible_availability_zones', 'eligible_cloud_networks', 'eligible_cloud_subnets', 'eligible_cloud_tenants', 'eligible_clusters', 'eligible_customization_templates', 'eligible_floating_ip_addresses', 'eligible_folders', 'eligible_guest_access_key_pairs', 'eligible_hosts', 'eligible_instance_types', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_security_groups', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 113 | 114 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 115 | 116 | 'MiqAeServiceManageIQ_Providers_Amazon_CloudManager_Vm': ['accounts', 'availability_zone', 'cloud_network', 'cloud_subnet', 'cloud_subnets', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'flavor', 'floating_ip', 'groups', 'guest_applications', 'hardware', 'host', 'key_pairs', 'miq_provision', 'network_ports', 'network_routers', 'operating_system', 'resource_pool', 'security_groups', 'service', 'snapshots', 'storage', 'tenant', 'users'] 117 | 118 | 'MiqAeServiceManageIQ_Providers_Amazon_NetworkManager': ['cloud_networks', 'cloud_subnets', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'floating_ips', 'hosts', 'miq_templates', 'network_ports', 'network_routers', 'parent_manager', 'private_networks', 'provider', 'public_networks', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 119 | 120 | 'MiqAeServiceManageIQ_Providers_Amazon_NetworkManager_CloudNetwork': ['cloud_subnets', 'cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'network_routers', 'security_groups', 'vms'] 121 | 122 | 'MiqAeServiceManageIQ_Providers_Amazon_NetworkManager_CloudSubnet': ['availability_zone', 'cloud_network', 'vms'] 123 | 124 | 'MiqAeServiceManageIQ_Providers_Amazon_NetworkManager_FloatingIp': ['cloud_tenant', 'ext_management_system', 'vm'] 125 | 126 | 'MiqAeServiceManageIQ_Providers_Amazon_NetworkManager_NetworkPort': ['cloud_subnet', 'cloud_tenant', 'device', 'ext_management_system'] 127 | 128 | 'MiqAeServiceManageIQ_Providers_Amazon_NetworkManager_NetworkRouter': ['cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'private_networks', 'public_network', 'vms'] 129 | 130 | 'MiqAeServiceManageIQ_Providers_Amazon_NetworkManager_SecurityGroup': ['cloud_network', 'cloud_tenant', 'ext_management_system', 'firewall_rules', 'vms'] 131 | 132 | 'MiqAeServiceManageIQ_Providers_AnsibleTower_ConfigurationManager': ['configuration_profiles', 'configuration_scripts', 'configured_systems', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 133 | 134 | 'MiqAeServiceManageIQ_Providers_AnsibleTower_ConfigurationManager_ConfigurationScript': ['inventory_root_group', 'manager'] 135 | 136 | 'MiqAeServiceManageIQ_Providers_AnsibleTower_ConfigurationManager_ConfiguredSystem': ['computer_system', 'configuration_location', 'configuration_profile', 'customization_script_media', 'customization_script_ptable', 'manager', 'operating_system_flavor'] 137 | 138 | 'MiqAeServiceManageIQ_Providers_AnsibleTower_ConfigurationManager_Job': ['ext_management_system', 'job_template', 'outputs', 'parameters', 'resources'] 139 | 140 | 'MiqAeServiceManageIQ_Providers_AnsibleTower_Provider': ['configuration_manager', 'managers', 'tenant', 'zone'] 141 | 142 | 'MiqAeServiceManageIQ_Providers_AtomicEnterprise_ContainerManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 143 | 144 | 'MiqAeServiceManageIQ_Providers_Atomic_ContainerManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 145 | 146 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager': ['availability_zones', 'cloud_networks', 'cloud_resource_quotas', 'cloud_tenants', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'flavors', 'floating_ips', 'hosts', 'key_pairs', 'miq_templates', 'network_manager', 'orchestration_stacks', 'private_networks', 'provider', 'public_networks', 'resource_groups', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 147 | 148 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_AvailabilityZone': ['cloud_subnets', 'ext_management_system', 'vms', 'vms_and_templates'] 149 | 150 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_Flavor': ['ext_management_system', 'vms'] 151 | 152 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_OrchestrationStack': ['cloud_networks', 'ext_management_system', 'orchestration_template', 'outputs', 'parameters', 'resources', 'security_groups', 'vms'] 153 | 154 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_Provision': ['destination', 'eligible_availability_zones', 'eligible_cloud_networks', 'eligible_cloud_subnets', 'eligible_cloud_tenants', 'eligible_clusters', 'eligible_customization_templates', 'eligible_floating_ip_addresses', 'eligible_folders', 'eligible_guest_access_key_pairs', 'eligible_hosts', 'eligible_instance_types', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_groups', 'eligible_resource_pools', 'eligible_security_groups', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 155 | 156 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 157 | 158 | 'MiqAeServiceManageIQ_Providers_Azure_CloudManager_Vm': ['accounts', 'availability_zone', 'cloud_network', 'cloud_networks', 'cloud_subnet', 'cloud_subnets', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'flavor', 'floating_ip', 'floating_ips', 'groups', 'guest_applications', 'hardware', 'host', 'key_pairs', 'miq_provision', 'network_ports', 'network_routers', 'operating_system', 'resource_pool', 'security_groups', 'service', 'snapshots', 'storage', 'tenant', 'users'] 159 | 160 | 'MiqAeServiceManageIQ_Providers_Azure_NetworkManager': ['cloud_networks', 'cloud_subnets', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'floating_ips', 'hosts', 'miq_templates', 'network_ports', 'network_routers', 'parent_manager', 'private_networks', 'provider', 'public_networks', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 161 | 162 | 'MiqAeServiceManageIQ_Providers_Azure_NetworkManager_CloudNetwork': ['cloud_subnets', 'cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'network_routers', 'security_groups', 'vms'] 163 | 164 | 'MiqAeServiceManageIQ_Providers_Azure_NetworkManager_CloudSubnet': ['availability_zone', 'cloud_network', 'vms'] 165 | 166 | 'MiqAeServiceManageIQ_Providers_Azure_NetworkManager_FloatingIp': ['cloud_tenant', 'ext_management_system', 'vm'] 167 | 168 | 'MiqAeServiceManageIQ_Providers_Azure_NetworkManager_NetworkPort': ['cloud_subnet', 'cloud_tenant', 'device', 'ext_management_system'] 169 | 170 | 'MiqAeServiceManageIQ_Providers_Azure_NetworkManager_NetworkRouter': ['cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'private_networks', 'public_network', 'vms'] 171 | 172 | 'MiqAeServiceManageIQ_Providers_Azure_NetworkManager_SecurityGroup': ['cloud_network', 'cloud_tenant', 'ext_management_system', 'firewall_rules', 'vms'] 173 | 174 | 'MiqAeServiceManageIQ_Providers_BaseManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 175 | 176 | 'MiqAeServiceManageIQ_Providers_CloudManager': ['availability_zones', 'cloud_networks', 'cloud_resource_quotas', 'cloud_tenants', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'flavors', 'floating_ips', 'hosts', 'key_pairs', 'miq_templates', 'network_manager', 'orchestration_stacks', 'private_networks', 'provider', 'public_networks', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 177 | 178 | 'MiqAeServiceManageIQ_Providers_CloudManager_OrchestrationStack': ['cloud_networks', 'ext_management_system', 'orchestration_template', 'outputs', 'parameters', 'resources', 'security_groups', 'vms'] 179 | 180 | 'MiqAeServiceManageIQ_Providers_CloudManager_Provision': ['destination', 'eligible_availability_zones', 'eligible_cloud_networks', 'eligible_cloud_subnets', 'eligible_cloud_tenants', 'eligible_clusters', 'eligible_customization_templates', 'eligible_floating_ip_addresses', 'eligible_folders', 'eligible_guest_access_key_pairs', 'eligible_hosts', 'eligible_instance_types', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_security_groups', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 181 | 182 | 'MiqAeServiceManageIQ_Providers_CloudManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 183 | 184 | 'MiqAeServiceManageIQ_Providers_CloudManager_Vm': ['accounts', 'availability_zone', 'cloud_network', 'cloud_subnet', 'cloud_subnets', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'flavor', 'floating_ip', 'groups', 'guest_applications', 'hardware', 'host', 'key_pairs', 'miq_provision', 'network_ports', 'network_routers', 'operating_system', 'resource_pool', 'security_groups', 'service', 'snapshots', 'storage', 'tenant', 'users'] 185 | 186 | 'MiqAeServiceManageIQ_Providers_ConfigurationManager': ['configuration_profiles', 'configured_systems', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 187 | 188 | 'MiqAeServiceManageIQ_Providers_ConfigurationManager_InventoryGroup': ['hosts', 'manager', 'vms'] 189 | 190 | 'MiqAeServiceManageIQ_Providers_ConfigurationManager_InventoryRootGroup': ['configuration_scripts', 'hosts', 'manager', 'vms'] 191 | 192 | 'MiqAeServiceManageIQ_Providers_ContainerManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 193 | 194 | 'MiqAeServiceManageIQ_Providers_Foreman_ConfigurationManager': ['configuration_profiles', 'configured_systems', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 195 | 196 | 'MiqAeServiceManageIQ_Providers_Foreman_ConfigurationManager_ConfigurationProfile': ['configuration_tags', 'configured_systems', 'customization_script_medium', 'customization_script_ptable', 'direct_configuration_tags', 'direct_customization_script_medium', 'direct_customization_script_ptable', 'direct_operating_system_flavors', 'manager', 'operating_system_flavors', 'parent', 'parent'] 197 | 198 | 'MiqAeServiceManageIQ_Providers_Foreman_ConfigurationManager_ConfiguredSystem': ['computer_system', 'configuration_location', 'configuration_profile', 'configuration_profile', 'configuration_tags', 'customization_script_media', 'customization_script_ptable', 'direct_configuration_tags', 'direct_customization_script_media', 'direct_customization_script_ptable', 'manager', 'operating_system_flavor'] 199 | 200 | 'MiqAeServiceManageIQ_Providers_Foreman_ConfigurationManager_ProvisionTask': ['destination', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant'] 201 | 202 | 'MiqAeServiceManageIQ_Providers_Foreman_Provider': ['configuration_manager', 'managers', 'provisioning_manager', 'tenant', 'zone'] 203 | 204 | 'MiqAeServiceManageIQ_Providers_Foreman_ProvisioningManager': ['customization_scripts', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'operating_system_flavors', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 205 | 206 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager': ['availability_zones', 'cloud_networks', 'cloud_resource_quotas', 'cloud_tenants', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'flavors', 'floating_ips', 'hosts', 'key_pairs', 'miq_templates', 'network_manager', 'orchestration_stacks', 'private_networks', 'provider', 'public_networks', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 207 | 208 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_AvailabilityZone': ['cloud_subnets', 'ext_management_system', 'vms', 'vms_and_templates'] 209 | 210 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_CloudVolumeSnapshot': ['based_volumes', 'cloud_tenant', 'cloud_volume', 'ext_management_system'] 211 | 212 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_Flavor': ['ext_management_system', 'vms'] 213 | 214 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_Provision': ['destination', 'eligible_availability_zones', 'eligible_cloud_networks', 'eligible_cloud_subnets', 'eligible_cloud_tenants', 'eligible_clusters', 'eligible_customization_templates', 'eligible_floating_ip_addresses', 'eligible_folders', 'eligible_guest_access_key_pairs', 'eligible_hosts', 'eligible_instance_types', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_security_groups', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 215 | 216 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_SecurityGroup': ['cloud_network', 'cloud_tenant', 'ext_management_system', 'firewall_rules', 'vms'] 217 | 218 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 219 | 220 | 'MiqAeServiceManageIQ_Providers_Google_CloudManager_Vm': ['accounts', 'availability_zone', 'cloud_network', 'cloud_subnet', 'cloud_subnets', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'flavor', 'floating_ip', 'groups', 'guest_applications', 'hardware', 'host', 'key_pairs', 'miq_provision', 'network_ports', 'network_routers', 'operating_system', 'resource_pool', 'security_groups', 'service', 'snapshots', 'storage', 'tenant', 'users'] 221 | 222 | 'MiqAeServiceManageIQ_Providers_InfraManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 223 | 224 | 'MiqAeServiceManageIQ_Providers_InfraManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 225 | 226 | 'MiqAeServiceManageIQ_Providers_InfraManager_Vm': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 227 | 228 | 'MiqAeServiceManageIQ_Providers_Kubernetes_ContainerManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 229 | 230 | 'MiqAeServiceManageIQ_Providers_Microsoft_InfraManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 231 | 232 | 'MiqAeServiceManageIQ_Providers_Microsoft_InfraManager_Host': ['datacenter', 'directories', 'ems_cluster', 'ems_events', 'ems_folder', 'ext_management_system', 'files', 'guest_applications', 'hardware', 'lans', 'operating_system', 'storages', 'switches', 'vms'] 233 | 234 | 'MiqAeServiceManageIQ_Providers_Microsoft_InfraManager_Provision': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 235 | 236 | 'MiqAeServiceManageIQ_Providers_Microsoft_InfraManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 237 | 238 | 'MiqAeServiceManageIQ_Providers_Microsoft_InfraManager_Vm': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 239 | 240 | 'MiqAeServiceManageIQ_Providers_MiddlewareManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 241 | 242 | 'MiqAeServiceManageIQ_Providers_NetworkManager': ['cloud_networks', 'cloud_subnets', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'floating_ips', 'hosts', 'miq_templates', 'network_ports', 'network_routers', 'parent_manager', 'private_networks', 'provider', 'public_networks', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 243 | 244 | 'MiqAeServiceManageIQ_Providers_OpenshiftEnterprise_ContainerManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 245 | 246 | 'MiqAeServiceManageIQ_Providers_Openshift_ContainerManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 247 | 248 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager': ['availability_zones', 'cloud_networks', 'cloud_resource_quotas', 'cloud_tenants', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'flavors', 'floating_ips', 'hosts', 'key_pairs', 'miq_templates', 'network_manager', 'orchestration_stacks', 'private_networks', 'provider', 'public_networks', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 249 | 250 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_AvailabilityZone': ['cloud_subnets', 'ext_management_system', 'vms', 'vms_and_templates'] 251 | 252 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_AvailabilityZoneNull': ['cloud_subnets', 'ext_management_system', 'vms', 'vms_and_templates'] 253 | 254 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_CloudResourceQuota': ['cloud_tenant', 'ext_management_system'] 255 | 256 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_CloudTenant': ['cloud_networks', 'cloud_resource_quotas', 'ext_management_system', 'floating_ips', 'miq_templates', 'security_groups', 'vms', 'vms_and_templates'] 257 | 258 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_CloudVolume': ['attachments', 'availability_zone', 'base_snapshot', 'cloud_tenant', 'cloud_volume_snapshots', 'ext_management_system'] 259 | 260 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_CloudVolumeSnapshot': ['based_volumes', 'cloud_tenant', 'cloud_volume', 'ext_management_system'] 261 | 262 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Flavor': ['ext_management_system', 'vms'] 263 | 264 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_OrchestrationStack': ['cloud_networks', 'ext_management_system', 'orchestration_template', 'outputs', 'parameters', 'resources', 'security_groups', 'vms'] 265 | 266 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Provision': ['destination', 'eligible_availability_zones', 'eligible_cloud_networks', 'eligible_cloud_subnets', 'eligible_cloud_tenants', 'eligible_clusters', 'eligible_customization_templates', 'eligible_floating_ip_addresses', 'eligible_folders', 'eligible_guest_access_key_pairs', 'eligible_hosts', 'eligible_instance_types', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_security_groups', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 267 | 268 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 269 | 270 | 'MiqAeServiceManageIQ_Providers_Openstack_CloudManager_Vm': ['accounts', 'availability_zone', 'cloud_network', 'cloud_networks', 'cloud_networks', 'cloud_subnet', 'cloud_subnets', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'flavor', 'floating_ip', 'floating_ips', 'groups', 'guest_applications', 'hardware', 'host', 'key_pairs', 'miq_provision', 'network_ports', 'network_routers', 'operating_system', 'resource_pool', 'security_groups', 'service', 'snapshots', 'storage', 'tenant', 'users'] 271 | 272 | 'MiqAeServiceManageIQ_Providers_Openstack_InfraManager': ['customization_specs', 'direct_orchestration_stacks', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'orchestration_stacks', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 273 | 274 | 'MiqAeServiceManageIQ_Providers_Openstack_InfraManager_EmsCluster': ['all_resource_pools', 'all_vms', 'default_resource_pool', 'ems_events', 'ext_management_system', 'hosts', 'parent_folder', 'resource_pools', 'storages', 'vms'] 275 | 276 | 'MiqAeServiceManageIQ_Providers_Openstack_InfraManager_Host': ['datacenter', 'directories', 'ems_cluster', 'ems_events', 'ems_folder', 'ext_management_system', 'files', 'guest_applications', 'hardware', 'lans', 'operating_system', 'storages', 'switches', 'vms'] 277 | 278 | 'MiqAeServiceManageIQ_Providers_Openstack_InfraManager_OrchestrationStack': ['ext_management_system', 'outputs', 'parameters', 'resources'] 279 | 280 | 'MiqAeServiceManageIQ_Providers_Openstack_InfraManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 281 | 282 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager': ['cloud_networks', 'cloud_subnets', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'floating_ips', 'hosts', 'miq_templates', 'network_ports', 'network_routers', 'parent_manager', 'private_networks', 'provider', 'public_networks', 'resource_pools', 'security_groups', 'storages', 'tenant', 'vms'] 283 | 284 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager_CloudNetwork': ['cloud_subnets', 'cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'network_routers', 'security_groups', 'vms'] 285 | 286 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager_CloudNetwork_Private': ['cloud_subnets', 'cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'network_routers', 'public_networks', 'security_groups', 'vms'] 287 | 288 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager_CloudNetwork_Public': ['cloud_subnets', 'cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'network_routers', 'private_networks', 'security_groups', 'vms'] 289 | 290 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager_CloudSubnet': ['availability_zone', 'cloud_network', 'vms'] 291 | 292 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager_FloatingIp': ['cloud_tenant', 'ext_management_system', 'vm'] 293 | 294 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager_NetworkPort': ['cloud_subnet', 'cloud_subnets', 'cloud_tenant', 'device', 'ext_management_system', 'network_routers', 'public_networks'] 295 | 296 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager_NetworkRouter': ['cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'private_networks', 'public_network', 'vms'] 297 | 298 | 'MiqAeServiceManageIQ_Providers_Openstack_NetworkManager_SecurityGroup': ['cloud_network', 'cloud_tenant', 'ext_management_system', 'firewall_rules', 'vms'] 299 | 300 | 'MiqAeServiceManageIQ_Providers_Openstack_Provider': ['cloud_ems', 'infra_ems', 'managers', 'tenant', 'zone'] 301 | 302 | 'MiqAeServiceManageIQ_Providers_ProvisioningManager': ['customization_scripts', 'customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'operating_system_flavors', 'provider', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 303 | 304 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 305 | 306 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Host': ['datacenter', 'directories', 'ems_cluster', 'ems_events', 'ems_folder', 'ext_management_system', 'files', 'guest_applications', 'hardware', 'lans', 'operating_system', 'storages', 'switches', 'vms'] 307 | 308 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Provision': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 309 | 310 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_ProvisionViaIso': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 311 | 312 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_ProvisionViaPxe': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 313 | 314 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 315 | 316 | 'MiqAeServiceManageIQ_Providers_Redhat_InfraManager_Vm': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 317 | 318 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager': ['customization_specs', 'ems_clusters', 'ems_events', 'ems_folders', 'hosts', 'miq_templates', 'provider', 'resource_pools', 'storages', 'tenant', 'vms'] 319 | 320 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Host': ['datacenter', 'directories', 'ems_MiqAeServiceEms', 'ems_events', 'ems_folder', 'ext_management_system', 'files', 'guest_applications', 'hardware', 'lans', 'operating_system', 'storages', 'switches', 'vms'] 321 | 322 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_HostEsx': ['datacenter', 'directories', 'ems_cluster', 'ems_events', 'ems_folder', 'ext_management_system', 'files', 'guest_applications', 'hardware', 'lans', 'operating_system', 'storages', 'switches', 'vms'] 323 | 324 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Provision': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 325 | 326 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_ProvisionViaPxe': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 327 | 328 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Template': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 329 | 330 | 'MiqAeServiceManageIQ_Providers_Vmware_InfraManager_Vm': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 331 | 332 | 'MiqAeServiceMiqEvent': ['dest_host', 'dest_vm', 'ems', 'ext_management_system', 'host', 'service', 'src_host', 'src_vm', 'vm'] 333 | 334 | 'MiqAeServiceMiqGroup': ['tenant', 'users', 'vms'] 335 | 336 | 'MiqAeServiceMiqHostProvision': ['destination', 'host', 'miq_host_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant'] 337 | 338 | 'MiqAeServiceMiqHostProvisionRequest': ['destination', 'miq_host_provisions', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant'] 339 | 340 | 'MiqAeServiceMiqProvision': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provision_request', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant', 'vm', 'vm_template'] 341 | 342 | 'MiqAeServiceMiqProvisionConfiguredSystemRequest': ['destination', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant'] 343 | 344 | 'MiqAeServiceMiqProvisionRequest': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provisions', 'miq_request', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant', 'vm_template'] 345 | 346 | 'MiqAeServiceMiqProvisionRequestTemplate': ['destination', 'eligible_clusters', 'eligible_customization_templates', 'eligible_folders', 'eligible_hosts', 'eligible_iso_images', 'eligible_pxe_images', 'eligible_pxe_servers', 'eligible_resource_pools', 'eligible_storages', 'eligible_windows_images', 'miq_provisions', 'miq_request', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant', 'vm_template'] 347 | 348 | 'MiqAeServiceMiqProvisionTask': ['destination', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant'] 349 | 350 | 'MiqAeServiceMiqRequest': ['destination', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant'] 351 | 352 | 'MiqAeServiceMiqRequestTask': ['destination', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant'] 353 | 354 | 'MiqAeServiceMiqTemplate': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 355 | 356 | 'MiqAeServiceNetwork': ['guest_device', 'hardware'] 357 | 358 | 'MiqAeServiceNetworkPort': ['cloud_subnet', 'cloud_tenant', 'device', 'ext_management_system'] 359 | 360 | 'MiqAeServiceNetworkRouter': ['cloud_tenant', 'ext_management_system', 'floating_ips', 'network_ports', 'private_networks', 'public_network', 'vms', 'vms'] 361 | 362 | 'MiqAeServiceOperatingSystemFlavor': ['provisioning_manager'] 363 | 364 | 'MiqAeServiceOrchestrationStack': ['ext_management_system', 'outputs', 'parameters', 'resources'] 365 | 366 | 'MiqAeServiceOrchestrationStackOutput': ['stack'] 367 | 368 | 'MiqAeServiceOrchestrationStackParameter': ['stack'] 369 | 370 | 'MiqAeServiceOrchestrationStackResource': ['stack'] 371 | 372 | 'MiqAeServiceOrchestrationTemplate': ['stacks'] 373 | 374 | 'MiqAeServiceOrchestrationTemplateAzure': ['stacks'] 375 | 376 | 'MiqAeServiceOrchestrationTemplateCfn': ['stacks'] 377 | 378 | 'MiqAeServiceOrchestrationTemplateHot': ['stacks'] 379 | 380 | 'MiqAeServiceProvider': ['managers', 'tenant', 'zone'] 381 | 382 | 'MiqAeServicePxeImage': ['customization_templates', 'pxe_server'] 383 | 384 | 'MiqAeServicePxeImageIpxe': ['customization_templates', 'pxe_server'] 385 | 386 | 'MiqAeServicePxeImagePxelinux': ['customization_templates', 'pxe_server'] 387 | 388 | 'MiqAeServicePxeImageType': ['customization_templates', 'iso_images', 'pxe_images', 'windows_images'] 389 | 390 | 'MiqAeServicePxeServer': ['advertised_images', 'advertised_pxe_images', 'default_pxe_image_for_windows', 'discovered_images', 'discovered_pxe_images', 'images', 'pxe_images', 'windows_images'] 391 | 392 | 'MiqAeServiceRequestEvent': ['dest_host', 'dest_vm', 'ems', 'ext_management_system', 'host', 'service', 'src_host', 'src_vm', 'vm'] 393 | 394 | 'MiqAeServiceSecurityGroup': ['cloud_network', 'cloud_tenant', 'ext_management_system', 'firewall_rules', 'vms'] 395 | 396 | 'MiqAeServiceService': ['all_service_children', 'direct_service_children', 'direct_vms', 'indirect_service_children', 'indirect_vms', 'parent_service', 'root_service', 'service_resources', 'service_template', 'tenant', 'vms'] 397 | 398 | 'MiqAeServiceServiceAnsibleTower': ['all_service_children', 'direct_service_children', 'direct_vms', 'indirect_service_children', 'indirect_vms', 'parent_service', 'root_service', 'service_resources', 'service_template', 'tenant', 'vms'] 399 | 400 | 'MiqAeServiceServiceOrchestration': ['all_service_children', 'direct_service_children', 'direct_vms', 'indirect_service_children', 'indirect_vms', 'parent_service', 'root_service', 'service_resources', 'service_template', 'tenant', 'vms'] 401 | 402 | 'MiqAeServiceServiceReconfigureRequest': ['destination', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant'] 403 | 404 | 'MiqAeServiceServiceReconfigureTask': ['destination', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant'] 405 | 406 | 'MiqAeServiceServiceResource': ['resource', 'service', 'service_template', 'source'] 407 | 408 | 'MiqAeServiceServiceTemplate': ['service_resources', 'service_templates', 'services', 'tenant'] 409 | 410 | 'MiqAeServiceServiceTemplateAnsibleTower': ['service_resources', 'service_templates', 'services', 'tenant'] 411 | 412 | 'MiqAeServiceServiceTemplateOrchestration': ['service_resources', 'service_templates', 'services', 'tenant'] 413 | 414 | 'MiqAeServiceServiceTemplateProvisionRequest': ['destination', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant'] 415 | 416 | 'MiqAeServiceServiceTemplateProvisionTask': ['destination', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'service_resource', 'source', 'tenant'] 417 | 418 | 'MiqAeServiceSnapshot': ['vm_or_template'] 419 | 420 | 'MiqAeServiceStorage': ['ext_management_systems', 'files', 'hosts', 'storage_clusters', 'storage_files', 'unregistered_vms', 'vms'] 421 | 422 | 'MiqAeServiceStorageCluster': ['hosts', 'vms'] 423 | 424 | 'MiqAeServiceStorageFile': ['miq_template', 'storage', 'vm', 'vm_or_template'] 425 | 426 | 'MiqAeServiceSwitch': ['guest_devices', 'host', 'lans'] 427 | 428 | 'MiqAeServiceTemplateCloud': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 429 | 430 | 'MiqAeServiceTemplateInfra': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 431 | 432 | 'MiqAeServiceTemplateXen': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 433 | 434 | 'MiqAeServiceTenant': ['ae_domains', 'ext_management_systems', 'miq_groups', 'miq_request_tasks', 'miq_requests', 'miq_templates', 'providers', 'service_templates', 'services', 'tenant_quotas', 'users', 'vm_or_templates', 'vms'] 435 | 436 | 'MiqAeServiceTenantQuota': ['tenant'] 437 | 438 | 'MiqAeServiceUser': ['current_group', 'current_tenant', 'miq_requests', 'vms'] 439 | 440 | 'MiqAeServiceVm': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 441 | 442 | 'MiqAeServiceVmCloud': ['accounts', 'availability_zone', 'cloud_network', 'cloud_networks', 'cloud_subnet', 'cloud_subnets', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'flavor', 'floating_ip', 'floating_ips', 'groups', 'guest_applications', 'hardware', 'host', 'key_pairs', 'miq_provision', 'network_ports', 'network_routers', 'operating_system', 'resource_pool', 'security_groups', 'service', 'snapshots', 'storage', 'tenant', 'users'] 443 | 444 | 'MiqAeServiceVmInfra': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 445 | 446 | 'MiqAeServiceVmMigrateRequest': ['destination', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant'] 447 | 448 | 'MiqAeServiceVmMigrateTask': ['destination', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant'] 449 | 450 | 'MiqAeServiceVmOrTemplate': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 451 | 452 | 'MiqAeServiceVmReconfigureRequest': ['destination', 'miq_request_tasks', 'requester', 'resource', 'source', 'tenant'] 453 | 454 | 'MiqAeServiceVmReconfigureTask': ['destination', 'miq_request', 'miq_request_task', 'miq_request_tasks', 'source', 'tenant'] 455 | 456 | 'MiqAeServiceVmXen': ['accounts', 'datacenter', 'direct_service', 'directories', 'ems_blue_folder', 'ems_cluster', 'ems_folder', 'ext_management_system', 'files', 'groups', 'guest_applications', 'hardware', 'host', 'miq_provision', 'operating_system', 'resource_pool', 'service', 'snapshots', 'storage', 'tenant', 'users'] 457 | 458 | 'MiqAeServiceWindowsImage': ['customization_templates', 'pxe_server'] 459 | --------------------------------------------------------------------------------