catocli 2.1.1__py3-none-any.whl → 2.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of catocli might be problematic. Click here for more details.

Files changed (564) hide show
  1. catocli/Utils/clidriver.py +207 -211
  2. catocli/Utils/cliutils.py +178 -0
  3. catocli/__init__.py +1 -1
  4. catocli/parsers/custom/customLib.py +1 -1
  5. catocli/parsers/custom/export_sites/export_sites.py +121 -133
  6. catocli/parsers/customParserApiClient.py +1195 -0
  7. catocli/parsers/custom_private/__init__.py +2 -14
  8. catocli/parsers/mutation_accountManagement/__init__.py +45 -58
  9. catocli/parsers/mutation_accountManagement_addAccount/README.md +7 -5
  10. catocli/parsers/mutation_accountManagement_removeAccount/README.md +4 -5
  11. catocli/parsers/mutation_accountManagement_updateAccount/README.md +7 -5
  12. catocli/parsers/mutation_admin/__init__.py +84 -45
  13. catocli/parsers/mutation_admin_addAdmin/README.md +7 -5
  14. catocli/parsers/mutation_admin_addServicePrincipalAdmin/README.md +19 -0
  15. catocli/parsers/mutation_admin_removeAdmin/README.md +7 -5
  16. catocli/parsers/mutation_admin_removeServicePrincipalAdmin/README.md +19 -0
  17. catocli/parsers/mutation_admin_updateAdmin/README.md +8 -6
  18. catocli/parsers/mutation_admin_updateServicePrincipalAdmin/README.md +20 -0
  19. catocli/parsers/mutation_container/__init__.py +135 -135
  20. catocli/parsers/mutation_container_delete/README.md +7 -5
  21. catocli/parsers/mutation_container_fqdn_addValues/README.md +7 -5
  22. catocli/parsers/mutation_container_fqdn_createFromFile/README.md +7 -5
  23. catocli/parsers/mutation_container_fqdn_removeValues/README.md +7 -5
  24. catocli/parsers/mutation_container_fqdn_updateFromFile/README.md +7 -5
  25. catocli/parsers/mutation_container_ipAddressRange_addValues/README.md +7 -5
  26. catocli/parsers/mutation_container_ipAddressRange_createFromFile/README.md +7 -5
  27. catocli/parsers/mutation_container_ipAddressRange_removeValues/README.md +7 -5
  28. catocli/parsers/mutation_container_ipAddressRange_updateFromFile/README.md +7 -5
  29. catocli/parsers/mutation_enterpriseDirectory/README.md +7 -0
  30. catocli/parsers/mutation_enterpriseDirectory/__init__.py +61 -0
  31. catocli/parsers/mutation_enterpriseDirectory_archiveLocation/README.md +19 -0
  32. catocli/parsers/mutation_enterpriseDirectory_createLocation/README.md +19 -0
  33. catocli/parsers/mutation_enterpriseDirectory_restoreLocation/README.md +19 -0
  34. catocli/parsers/mutation_enterpriseDirectory_updateLocation/README.md +19 -0
  35. catocli/parsers/mutation_groups/__init__.py +45 -45
  36. catocli/parsers/mutation_groups_createGroup/README.md +8 -6
  37. catocli/parsers/mutation_groups_deleteGroup/README.md +8 -6
  38. catocli/parsers/mutation_groups_updateGroup/README.md +8 -6
  39. catocli/parsers/mutation_hardware/__init__.py +16 -16
  40. catocli/parsers/mutation_hardware_updateHardwareShipping/README.md +7 -5
  41. catocli/parsers/mutation_policy/__init__.py +1302 -1302
  42. catocli/parsers/mutation_policy_appTenantRestriction_addRule/README.md +8 -6
  43. catocli/parsers/mutation_policy_appTenantRestriction_addSection/README.md +8 -6
  44. catocli/parsers/mutation_policy_appTenantRestriction_createPolicyRevision/README.md +8 -6
  45. catocli/parsers/mutation_policy_appTenantRestriction_discardPolicyRevision/README.md +8 -6
  46. catocli/parsers/mutation_policy_appTenantRestriction_moveRule/README.md +8 -6
  47. catocli/parsers/mutation_policy_appTenantRestriction_moveSection/README.md +8 -6
  48. catocli/parsers/mutation_policy_appTenantRestriction_publishPolicyRevision/README.md +8 -6
  49. catocli/parsers/mutation_policy_appTenantRestriction_removeRule/README.md +8 -6
  50. catocli/parsers/mutation_policy_appTenantRestriction_removeSection/README.md +8 -6
  51. catocli/parsers/mutation_policy_appTenantRestriction_updatePolicy/README.md +8 -6
  52. catocli/parsers/mutation_policy_appTenantRestriction_updateRule/README.md +8 -6
  53. catocli/parsers/mutation_policy_appTenantRestriction_updateSection/README.md +8 -6
  54. catocli/parsers/mutation_policy_dynamicIpAllocation_addRule/README.md +8 -6
  55. catocli/parsers/mutation_policy_dynamicIpAllocation_addSection/README.md +8 -6
  56. catocli/parsers/mutation_policy_dynamicIpAllocation_createPolicyRevision/README.md +8 -6
  57. catocli/parsers/mutation_policy_dynamicIpAllocation_discardPolicyRevision/README.md +8 -6
  58. catocli/parsers/mutation_policy_dynamicIpAllocation_moveRule/README.md +8 -6
  59. catocli/parsers/mutation_policy_dynamicIpAllocation_moveSection/README.md +8 -6
  60. catocli/parsers/mutation_policy_dynamicIpAllocation_publishPolicyRevision/README.md +8 -6
  61. catocli/parsers/mutation_policy_dynamicIpAllocation_removeRule/README.md +8 -6
  62. catocli/parsers/mutation_policy_dynamicIpAllocation_removeSection/README.md +8 -6
  63. catocli/parsers/mutation_policy_dynamicIpAllocation_updatePolicy/README.md +8 -6
  64. catocli/parsers/mutation_policy_dynamicIpAllocation_updateRule/README.md +8 -6
  65. catocli/parsers/mutation_policy_dynamicIpAllocation_updateSection/README.md +8 -6
  66. catocli/parsers/mutation_policy_internetFirewall_addRule/README.md +8 -6
  67. catocli/parsers/mutation_policy_internetFirewall_addSection/README.md +8 -6
  68. catocli/parsers/mutation_policy_internetFirewall_createPolicyRevision/README.md +8 -6
  69. catocli/parsers/mutation_policy_internetFirewall_discardPolicyRevision/README.md +8 -6
  70. catocli/parsers/mutation_policy_internetFirewall_moveRule/README.md +8 -6
  71. catocli/parsers/mutation_policy_internetFirewall_moveSection/README.md +8 -6
  72. catocli/parsers/mutation_policy_internetFirewall_publishPolicyRevision/README.md +8 -6
  73. catocli/parsers/mutation_policy_internetFirewall_removeRule/README.md +8 -6
  74. catocli/parsers/mutation_policy_internetFirewall_removeSection/README.md +8 -6
  75. catocli/parsers/mutation_policy_internetFirewall_updatePolicy/README.md +8 -6
  76. catocli/parsers/mutation_policy_internetFirewall_updateRule/README.md +8 -6
  77. catocli/parsers/mutation_policy_internetFirewall_updateSection/README.md +8 -6
  78. catocli/parsers/mutation_policy_remotePortFwd_addRule/README.md +8 -6
  79. catocli/parsers/mutation_policy_remotePortFwd_addSection/README.md +8 -6
  80. catocli/parsers/mutation_policy_remotePortFwd_createPolicyRevision/README.md +8 -6
  81. catocli/parsers/mutation_policy_remotePortFwd_discardPolicyRevision/README.md +8 -6
  82. catocli/parsers/mutation_policy_remotePortFwd_moveRule/README.md +8 -6
  83. catocli/parsers/mutation_policy_remotePortFwd_moveSection/README.md +8 -6
  84. catocli/parsers/mutation_policy_remotePortFwd_publishPolicyRevision/README.md +8 -6
  85. catocli/parsers/mutation_policy_remotePortFwd_removeRule/README.md +8 -6
  86. catocli/parsers/mutation_policy_remotePortFwd_removeSection/README.md +8 -6
  87. catocli/parsers/mutation_policy_remotePortFwd_updatePolicy/README.md +8 -6
  88. catocli/parsers/mutation_policy_remotePortFwd_updateRule/README.md +8 -6
  89. catocli/parsers/mutation_policy_remotePortFwd_updateSection/README.md +8 -6
  90. catocli/parsers/mutation_policy_socketLan_addRule/README.md +8 -6
  91. catocli/parsers/mutation_policy_socketLan_addSection/README.md +8 -6
  92. catocli/parsers/mutation_policy_socketLan_createPolicyRevision/README.md +8 -6
  93. catocli/parsers/mutation_policy_socketLan_discardPolicyRevision/README.md +8 -6
  94. catocli/parsers/mutation_policy_socketLan_moveRule/README.md +8 -6
  95. catocli/parsers/mutation_policy_socketLan_moveSection/README.md +8 -6
  96. catocli/parsers/mutation_policy_socketLan_publishPolicyRevision/README.md +8 -6
  97. catocli/parsers/mutation_policy_socketLan_removeRule/README.md +8 -6
  98. catocli/parsers/mutation_policy_socketLan_removeSection/README.md +8 -6
  99. catocli/parsers/mutation_policy_socketLan_updatePolicy/README.md +8 -6
  100. catocli/parsers/mutation_policy_socketLan_updateRule/README.md +8 -6
  101. catocli/parsers/mutation_policy_socketLan_updateSection/README.md +8 -6
  102. catocli/parsers/mutation_policy_terminalServer_addRule/README.md +8 -6
  103. catocli/parsers/mutation_policy_terminalServer_addSection/README.md +8 -6
  104. catocli/parsers/mutation_policy_terminalServer_createPolicyRevision/README.md +8 -6
  105. catocli/parsers/mutation_policy_terminalServer_discardPolicyRevision/README.md +8 -6
  106. catocli/parsers/mutation_policy_terminalServer_moveRule/README.md +8 -6
  107. catocli/parsers/mutation_policy_terminalServer_moveSection/README.md +8 -6
  108. catocli/parsers/mutation_policy_terminalServer_publishPolicyRevision/README.md +8 -6
  109. catocli/parsers/mutation_policy_terminalServer_removeRule/README.md +8 -6
  110. catocli/parsers/mutation_policy_terminalServer_removeSection/README.md +8 -6
  111. catocli/parsers/mutation_policy_terminalServer_updatePolicy/README.md +8 -6
  112. catocli/parsers/mutation_policy_terminalServer_updateRule/README.md +8 -6
  113. catocli/parsers/mutation_policy_terminalServer_updateSection/README.md +8 -6
  114. catocli/parsers/mutation_policy_wanFirewall_addRule/README.md +8 -6
  115. catocli/parsers/mutation_policy_wanFirewall_addSection/README.md +8 -6
  116. catocli/parsers/mutation_policy_wanFirewall_createPolicyRevision/README.md +8 -6
  117. catocli/parsers/mutation_policy_wanFirewall_discardPolicyRevision/README.md +8 -6
  118. catocli/parsers/mutation_policy_wanFirewall_moveRule/README.md +8 -6
  119. catocli/parsers/mutation_policy_wanFirewall_moveSection/README.md +8 -6
  120. catocli/parsers/mutation_policy_wanFirewall_publishPolicyRevision/README.md +8 -6
  121. catocli/parsers/mutation_policy_wanFirewall_removeRule/README.md +8 -6
  122. catocli/parsers/mutation_policy_wanFirewall_removeSection/README.md +8 -6
  123. catocli/parsers/mutation_policy_wanFirewall_updatePolicy/README.md +8 -6
  124. catocli/parsers/mutation_policy_wanFirewall_updateRule/README.md +8 -6
  125. catocli/parsers/mutation_policy_wanFirewall_updateSection/README.md +8 -6
  126. catocli/parsers/mutation_policy_wanNetwork_addRule/README.md +8 -6
  127. catocli/parsers/mutation_policy_wanNetwork_addSection/README.md +8 -6
  128. catocli/parsers/mutation_policy_wanNetwork_createPolicyRevision/README.md +8 -6
  129. catocli/parsers/mutation_policy_wanNetwork_discardPolicyRevision/README.md +8 -6
  130. catocli/parsers/mutation_policy_wanNetwork_moveRule/README.md +8 -6
  131. catocli/parsers/mutation_policy_wanNetwork_moveSection/README.md +8 -6
  132. catocli/parsers/mutation_policy_wanNetwork_publishPolicyRevision/README.md +8 -6
  133. catocli/parsers/mutation_policy_wanNetwork_removeRule/README.md +8 -6
  134. catocli/parsers/mutation_policy_wanNetwork_removeSection/README.md +8 -6
  135. catocli/parsers/mutation_policy_wanNetwork_updatePolicy/README.md +8 -6
  136. catocli/parsers/mutation_policy_wanNetwork_updateRule/README.md +8 -6
  137. catocli/parsers/mutation_policy_wanNetwork_updateSection/README.md +8 -6
  138. catocli/parsers/mutation_sandbox/__init__.py +27 -27
  139. catocli/parsers/mutation_sandbox_deleteReport/README.md +7 -5
  140. catocli/parsers/mutation_sandbox_uploadFile/README.md +7 -5
  141. catocli/parsers/mutation_site/__init__.py +474 -474
  142. catocli/parsers/mutation_site_addBgpPeer/README.md +7 -5
  143. catocli/parsers/mutation_site_addCloudInterconnectPhysicalConnection/README.md +7 -5
  144. catocli/parsers/mutation_site_addCloudInterconnectSite/README.md +7 -5
  145. catocli/parsers/mutation_site_addIpsecIkeV2Site/README.md +7 -5
  146. catocli/parsers/mutation_site_addIpsecIkeV2SiteTunnels/README.md +8 -6
  147. catocli/parsers/mutation_site_addNetworkRange/README.md +8 -6
  148. catocli/parsers/mutation_site_addSecondaryAwsVSocket/README.md +7 -5
  149. catocli/parsers/mutation_site_addSecondaryAzureVSocket/README.md +7 -5
  150. catocli/parsers/mutation_site_addSocketAddOnCard/README.md +7 -5
  151. catocli/parsers/mutation_site_addSocketSite/README.md +7 -5
  152. catocli/parsers/mutation_site_addStaticHost/README.md +8 -6
  153. catocli/parsers/mutation_site_assignSiteBwLicense/README.md +7 -5
  154. catocli/parsers/mutation_site_removeBgpPeer/README.md +7 -5
  155. catocli/parsers/mutation_site_removeCloudInterconnectPhysicalConnection/README.md +7 -5
  156. catocli/parsers/mutation_site_removeIpsecIkeV2SiteTunnels/README.md +8 -6
  157. catocli/parsers/mutation_site_removeNetworkRange/README.md +7 -5
  158. catocli/parsers/mutation_site_removeSecondaryAwsVSocket/README.md +7 -5
  159. catocli/parsers/mutation_site_removeSecondaryAzureVSocket/README.md +7 -5
  160. catocli/parsers/mutation_site_removeSite/README.md +7 -5
  161. catocli/parsers/mutation_site_removeSiteBwLicense/README.md +7 -5
  162. catocli/parsers/mutation_site_removeSocketAddOnCard/README.md +7 -5
  163. catocli/parsers/mutation_site_removeStaticHost/README.md +7 -5
  164. catocli/parsers/mutation_site_replaceSiteBwLicense/README.md +7 -5
  165. catocli/parsers/mutation_site_startSiteUpgrade/README.md +7 -5
  166. catocli/parsers/mutation_site_updateBgpPeer/README.md +7 -5
  167. catocli/parsers/mutation_site_updateCloudInterconnectPhysicalConnection/README.md +7 -5
  168. catocli/parsers/mutation_site_updateHa/README.md +8 -6
  169. catocli/parsers/mutation_site_updateIpsecIkeV2SiteGeneralDetails/README.md +8 -6
  170. catocli/parsers/mutation_site_updateIpsecIkeV2SiteTunnels/README.md +8 -6
  171. catocli/parsers/mutation_site_updateNetworkRange/README.md +8 -6
  172. catocli/parsers/mutation_site_updateSecondaryAwsVSocket/README.md +7 -5
  173. catocli/parsers/mutation_site_updateSecondaryAzureVSocket/README.md +7 -5
  174. catocli/parsers/mutation_site_updateSiteBwLicense/README.md +7 -5
  175. catocli/parsers/mutation_site_updateSiteGeneralDetails/README.md +8 -6
  176. catocli/parsers/mutation_site_updateSocketInterface/README.md +9 -7
  177. catocli/parsers/mutation_site_updateStaticHost/README.md +8 -6
  178. catocli/parsers/mutation_sites/__init__.py +474 -474
  179. catocli/parsers/mutation_sites_addBgpPeer/README.md +7 -5
  180. catocli/parsers/mutation_sites_addCloudInterconnectPhysicalConnection/README.md +7 -5
  181. catocli/parsers/mutation_sites_addCloudInterconnectSite/README.md +7 -5
  182. catocli/parsers/mutation_sites_addIpsecIkeV2Site/README.md +7 -5
  183. catocli/parsers/mutation_sites_addIpsecIkeV2SiteTunnels/README.md +8 -6
  184. catocli/parsers/mutation_sites_addNetworkRange/README.md +8 -6
  185. catocli/parsers/mutation_sites_addSecondaryAwsVSocket/README.md +7 -5
  186. catocli/parsers/mutation_sites_addSecondaryAzureVSocket/README.md +7 -5
  187. catocli/parsers/mutation_sites_addSocketAddOnCard/README.md +7 -5
  188. catocli/parsers/mutation_sites_addSocketSite/README.md +7 -5
  189. catocli/parsers/mutation_sites_addStaticHost/README.md +8 -6
  190. catocli/parsers/mutation_sites_assignSiteBwLicense/README.md +7 -5
  191. catocli/parsers/mutation_sites_removeBgpPeer/README.md +7 -5
  192. catocli/parsers/mutation_sites_removeCloudInterconnectPhysicalConnection/README.md +7 -5
  193. catocli/parsers/mutation_sites_removeIpsecIkeV2SiteTunnels/README.md +8 -6
  194. catocli/parsers/mutation_sites_removeNetworkRange/README.md +7 -5
  195. catocli/parsers/mutation_sites_removeSecondaryAwsVSocket/README.md +7 -5
  196. catocli/parsers/mutation_sites_removeSecondaryAzureVSocket/README.md +7 -5
  197. catocli/parsers/mutation_sites_removeSite/README.md +7 -5
  198. catocli/parsers/mutation_sites_removeSiteBwLicense/README.md +7 -5
  199. catocli/parsers/mutation_sites_removeSocketAddOnCard/README.md +7 -5
  200. catocli/parsers/mutation_sites_removeStaticHost/README.md +7 -5
  201. catocli/parsers/mutation_sites_replaceSiteBwLicense/README.md +7 -5
  202. catocli/parsers/mutation_sites_startSiteUpgrade/README.md +7 -5
  203. catocli/parsers/mutation_sites_updateBgpPeer/README.md +7 -5
  204. catocli/parsers/mutation_sites_updateCloudInterconnectPhysicalConnection/README.md +7 -5
  205. catocli/parsers/mutation_sites_updateHa/README.md +8 -6
  206. catocli/parsers/mutation_sites_updateIpsecIkeV2SiteGeneralDetails/README.md +8 -6
  207. catocli/parsers/mutation_sites_updateIpsecIkeV2SiteTunnels/README.md +8 -6
  208. catocli/parsers/mutation_sites_updateNetworkRange/README.md +8 -6
  209. catocli/parsers/mutation_sites_updateSecondaryAwsVSocket/README.md +7 -5
  210. catocli/parsers/mutation_sites_updateSecondaryAzureVSocket/README.md +7 -5
  211. catocli/parsers/mutation_sites_updateSiteBwLicense/README.md +7 -5
  212. catocli/parsers/mutation_sites_updateSiteGeneralDetails/README.md +8 -6
  213. catocli/parsers/mutation_sites_updateSocketInterface/README.md +9 -7
  214. catocli/parsers/mutation_sites_updateStaticHost/README.md +8 -6
  215. catocli/parsers/mutation_xdr/__init__.py +45 -45
  216. catocli/parsers/mutation_xdr_addStoryComment/README.md +7 -5
  217. catocli/parsers/mutation_xdr_analystFeedback/README.md +7 -6
  218. catocli/parsers/mutation_xdr_deleteStoryComment/README.md +7 -5
  219. catocli/parsers/query_accountBySubdomain/README.md +7 -5
  220. catocli/parsers/query_accountBySubdomain/__init__.py +12 -12
  221. catocli/parsers/query_accountManagement/README.md +4 -5
  222. catocli/parsers/query_accountManagement/__init__.py +12 -12
  223. catocli/parsers/query_accountMetrics/README.md +20 -18
  224. catocli/parsers/query_accountMetrics/__init__.py +12 -12
  225. catocli/parsers/query_accountRoles/README.md +7 -5
  226. catocli/parsers/query_accountRoles/__init__.py +12 -12
  227. catocli/parsers/query_accountSnapshot/README.md +8 -6
  228. catocli/parsers/query_accountSnapshot/__init__.py +12 -12
  229. catocli/parsers/query_admin/README.md +7 -5
  230. catocli/parsers/query_admin/__init__.py +12 -12
  231. catocli/parsers/query_admins/README.md +11 -9
  232. catocli/parsers/query_admins/__init__.py +12 -12
  233. catocli/parsers/query_appStats/README.md +20 -11
  234. catocli/parsers/query_appStats/__init__.py +12 -12
  235. catocli/parsers/query_appStatsTimeSeries/README.md +21 -12
  236. catocli/parsers/query_appStatsTimeSeries/__init__.py +12 -12
  237. catocli/parsers/query_auditFeed/README.md +10 -8
  238. catocli/parsers/query_auditFeed/__init__.py +12 -12
  239. catocli/parsers/query_catalogs/README.md +9 -7
  240. catocli/parsers/query_catalogs/__init__.py +12 -12
  241. catocli/parsers/query_container/README.md +13 -11
  242. catocli/parsers/query_container/__init__.py +12 -12
  243. catocli/parsers/query_devices/README.md +10 -8
  244. catocli/parsers/query_devices/__init__.py +12 -12
  245. catocli/parsers/query_enterpriseDirectory/README.md +19 -0
  246. catocli/parsers/query_enterpriseDirectory/__init__.py +16 -0
  247. catocli/parsers/query_entityLookup/README.md +19 -17
  248. catocli/parsers/query_entityLookup/__init__.py +12 -12
  249. catocli/parsers/query_events/README.md +13 -11
  250. catocli/parsers/query_events/__init__.py +12 -12
  251. catocli/parsers/query_eventsFeed/README.md +9 -7
  252. catocli/parsers/query_eventsFeed/__init__.py +12 -12
  253. catocli/parsers/query_eventsTimeSeries/README.md +14 -12
  254. catocli/parsers/query_eventsTimeSeries/__init__.py +12 -12
  255. catocli/parsers/query_groups/__init__.py +51 -51
  256. catocli/parsers/query_groups_groupList/README.md +8 -6
  257. catocli/parsers/query_groups_group_members/README.md +8 -6
  258. catocli/parsers/query_groups_whereUsed/README.md +7 -5
  259. catocli/parsers/query_hardware/README.md +7 -5
  260. catocli/parsers/query_hardware/__init__.py +12 -12
  261. catocli/parsers/query_hardwareManagement/README.md +7 -5
  262. catocli/parsers/query_hardwareManagement/__init__.py +12 -12
  263. catocli/parsers/query_licensing/README.md +4 -5
  264. catocli/parsers/query_licensing/__init__.py +12 -12
  265. catocli/parsers/query_policy/__init__.py +158 -158
  266. catocli/parsers/query_policy_appTenantRestriction_policy/README.md +7 -5
  267. catocli/parsers/query_policy_dynamicIpAllocation_policy/README.md +7 -5
  268. catocli/parsers/query_policy_internetFirewall_policy/README.md +7 -5
  269. catocli/parsers/query_policy_remotePortFwd_policy/README.md +7 -5
  270. catocli/parsers/query_policy_socketLan_policy/README.md +7 -5
  271. catocli/parsers/query_policy_terminalServer_policy/README.md +7 -5
  272. catocli/parsers/query_policy_wanFirewall_policy/README.md +7 -5
  273. catocli/parsers/query_policy_wanNetwork_policy/README.md +7 -5
  274. catocli/parsers/query_popLocations/README.md +7 -5
  275. catocli/parsers/query_popLocations/__init__.py +12 -12
  276. catocli/parsers/query_sandbox/README.md +7 -5
  277. catocli/parsers/query_sandbox/__init__.py +12 -12
  278. catocli/parsers/query_servicePrincipalAdmin/README.md +19 -0
  279. catocli/parsers/query_servicePrincipalAdmin/__init__.py +16 -0
  280. catocli/parsers/query_site/__init__.py +123 -123
  281. catocli/parsers/query_siteLocation/__init__.py +10 -10
  282. catocli/parsers/query_site_availableVersionList/README.md +7 -5
  283. catocli/parsers/query_site_bgpPeer/README.md +7 -5
  284. catocli/parsers/query_site_bgpPeerList/README.md +7 -5
  285. catocli/parsers/query_site_cloudInterconnectConnectionConnectivity/README.md +7 -5
  286. catocli/parsers/query_site_cloudInterconnectPhysicalConnection/README.md +7 -5
  287. catocli/parsers/query_site_cloudInterconnectPhysicalConnectionId/README.md +7 -5
  288. catocli/parsers/query_site_secondaryAwsVSocket/README.md +7 -5
  289. catocli/parsers/query_site_secondaryAzureVSocket/README.md +7 -5
  290. catocli/parsers/query_site_siteBgpStatus/README.md +7 -5
  291. catocli/parsers/query_socketPortMetrics/README.md +13 -11
  292. catocli/parsers/query_socketPortMetrics/__init__.py +12 -12
  293. catocli/parsers/query_socketPortMetricsTimeSeries/README.md +14 -12
  294. catocli/parsers/query_socketPortMetricsTimeSeries/__init__.py +12 -12
  295. catocli/parsers/query_subDomains/README.md +7 -5
  296. catocli/parsers/query_subDomains/__init__.py +12 -12
  297. catocli/parsers/query_xdr/__init__.py +27 -27
  298. catocli/parsers/query_xdr_stories/README.md +7 -6
  299. catocli/parsers/query_xdr_story/README.md +9 -8
  300. catocli/parsers/raw/__init__.py +9 -9
  301. {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/METADATA +1 -1
  302. catocli-2.1.3.dist-info/RECORD +660 -0
  303. models/mutation.accountManagement.addAccount.json +5 -15
  304. models/mutation.accountManagement.updateAccount.json +1 -3
  305. models/mutation.admin.addAdmin.json +14 -35
  306. models/mutation.admin.addServicePrincipalAdmin.json +2554 -0
  307. models/mutation.admin.removeAdmin.json +1 -1
  308. models/mutation.admin.removeServicePrincipalAdmin.json +201 -0
  309. models/mutation.admin.updateAdmin.json +13 -30
  310. models/mutation.admin.updateServicePrincipalAdmin.json +2554 -0
  311. models/mutation.container.delete.json +9 -435
  312. models/mutation.container.fqdn.addValues.json +6 -11
  313. models/mutation.container.fqdn.createFromFile.json +4 -12
  314. models/mutation.container.fqdn.removeValues.json +6 -11
  315. models/mutation.container.fqdn.updateFromFile.json +5 -15
  316. models/mutation.container.ipAddressRange.addValues.json +4 -12
  317. models/mutation.container.ipAddressRange.createFromFile.json +4 -12
  318. models/mutation.container.ipAddressRange.removeValues.json +4 -12
  319. models/mutation.container.ipAddressRange.updateFromFile.json +5 -15
  320. models/mutation.enterpriseDirectory.archiveLocation.json +1046 -0
  321. models/mutation.enterpriseDirectory.createLocation.json +2521 -0
  322. models/mutation.enterpriseDirectory.restoreLocation.json +1046 -0
  323. models/mutation.enterpriseDirectory.updateLocation.json +2567 -0
  324. models/mutation.groups.createGroup.json +39 -171
  325. models/mutation.groups.deleteGroup.json +36 -162
  326. models/mutation.groups.updateGroup.json +41 -177
  327. models/mutation.hardware.updateHardwareShipping.json +18 -47
  328. models/mutation.policy.appTenantRestriction.addRule.json +50 -136
  329. models/mutation.policy.appTenantRestriction.addSection.json +4 -12
  330. models/mutation.policy.appTenantRestriction.createPolicyRevision.json +3 -9
  331. models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +2 -6
  332. models/mutation.policy.appTenantRestriction.moveRule.json +4 -12
  333. models/mutation.policy.appTenantRestriction.moveSection.json +4 -12
  334. models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +3 -9
  335. models/mutation.policy.appTenantRestriction.removeRule.json +2 -6
  336. models/mutation.policy.appTenantRestriction.removeSection.json +2 -6
  337. models/mutation.policy.appTenantRestriction.updatePolicy.json +2 -6
  338. models/mutation.policy.appTenantRestriction.updateRule.json +49 -133
  339. models/mutation.policy.appTenantRestriction.updateSection.json +3 -9
  340. models/mutation.policy.dynamicIpAllocation.addRule.json +15 -45
  341. models/mutation.policy.dynamicIpAllocation.addSection.json +4 -12
  342. models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +3 -9
  343. models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +2 -6
  344. models/mutation.policy.dynamicIpAllocation.moveRule.json +4 -12
  345. models/mutation.policy.dynamicIpAllocation.moveSection.json +4 -12
  346. models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +3 -9
  347. models/mutation.policy.dynamicIpAllocation.removeRule.json +2 -6
  348. models/mutation.policy.dynamicIpAllocation.removeSection.json +2 -6
  349. models/mutation.policy.dynamicIpAllocation.updatePolicy.json +2 -6
  350. models/mutation.policy.dynamicIpAllocation.updateRule.json +14 -42
  351. models/mutation.policy.dynamicIpAllocation.updateSection.json +3 -9
  352. models/mutation.policy.internetFirewall.addRule.json +248 -542
  353. models/mutation.policy.internetFirewall.addSection.json +4 -12
  354. models/mutation.policy.internetFirewall.createPolicyRevision.json +3 -9
  355. models/mutation.policy.internetFirewall.discardPolicyRevision.json +2 -6
  356. models/mutation.policy.internetFirewall.moveRule.json +4 -12
  357. models/mutation.policy.internetFirewall.moveSection.json +4 -12
  358. models/mutation.policy.internetFirewall.publishPolicyRevision.json +3 -9
  359. models/mutation.policy.internetFirewall.removeRule.json +2 -6
  360. models/mutation.policy.internetFirewall.removeSection.json +2 -6
  361. models/mutation.policy.internetFirewall.updatePolicy.json +2 -6
  362. models/mutation.policy.internetFirewall.updateRule.json +247 -539
  363. models/mutation.policy.internetFirewall.updateSection.json +3 -9
  364. models/mutation.policy.remotePortFwd.addRule.json +35 -91
  365. models/mutation.policy.remotePortFwd.addSection.json +4 -12
  366. models/mutation.policy.remotePortFwd.createPolicyRevision.json +3 -9
  367. models/mutation.policy.remotePortFwd.discardPolicyRevision.json +2 -6
  368. models/mutation.policy.remotePortFwd.moveRule.json +4 -12
  369. models/mutation.policy.remotePortFwd.moveSection.json +4 -12
  370. models/mutation.policy.remotePortFwd.publishPolicyRevision.json +3 -9
  371. models/mutation.policy.remotePortFwd.removeRule.json +2 -6
  372. models/mutation.policy.remotePortFwd.removeSection.json +2 -6
  373. models/mutation.policy.remotePortFwd.updatePolicy.json +2 -6
  374. models/mutation.policy.remotePortFwd.updateRule.json +34 -88
  375. models/mutation.policy.remotePortFwd.updateSection.json +3 -9
  376. models/mutation.policy.socketLan.addRule.json +78 -185
  377. models/mutation.policy.socketLan.addSection.json +4 -12
  378. models/mutation.policy.socketLan.createPolicyRevision.json +3 -9
  379. models/mutation.policy.socketLan.discardPolicyRevision.json +2 -6
  380. models/mutation.policy.socketLan.moveRule.json +4 -12
  381. models/mutation.policy.socketLan.moveSection.json +4 -12
  382. models/mutation.policy.socketLan.publishPolicyRevision.json +3 -9
  383. models/mutation.policy.socketLan.removeRule.json +2 -6
  384. models/mutation.policy.socketLan.removeSection.json +2 -6
  385. models/mutation.policy.socketLan.updatePolicy.json +2 -6
  386. models/mutation.policy.socketLan.updateRule.json +77 -182
  387. models/mutation.policy.socketLan.updateSection.json +3 -9
  388. models/mutation.policy.terminalServer.addRule.json +10 -30
  389. models/mutation.policy.terminalServer.addSection.json +4 -12
  390. models/mutation.policy.terminalServer.createPolicyRevision.json +3 -9
  391. models/mutation.policy.terminalServer.discardPolicyRevision.json +2 -6
  392. models/mutation.policy.terminalServer.moveRule.json +4 -12
  393. models/mutation.policy.terminalServer.moveSection.json +4 -12
  394. models/mutation.policy.terminalServer.publishPolicyRevision.json +3 -9
  395. models/mutation.policy.terminalServer.removeRule.json +2 -6
  396. models/mutation.policy.terminalServer.removeSection.json +2 -6
  397. models/mutation.policy.terminalServer.updatePolicy.json +2 -6
  398. models/mutation.policy.terminalServer.updateRule.json +9 -27
  399. models/mutation.policy.terminalServer.updateSection.json +3 -9
  400. models/mutation.policy.wanFirewall.addRule.json +290 -654
  401. models/mutation.policy.wanFirewall.addSection.json +4 -12
  402. models/mutation.policy.wanFirewall.createPolicyRevision.json +3 -9
  403. models/mutation.policy.wanFirewall.discardPolicyRevision.json +2 -6
  404. models/mutation.policy.wanFirewall.moveRule.json +4 -12
  405. models/mutation.policy.wanFirewall.moveSection.json +4 -12
  406. models/mutation.policy.wanFirewall.publishPolicyRevision.json +3 -9
  407. models/mutation.policy.wanFirewall.removeRule.json +2 -6
  408. models/mutation.policy.wanFirewall.removeSection.json +2 -6
  409. models/mutation.policy.wanFirewall.updatePolicy.json +2 -6
  410. models/mutation.policy.wanFirewall.updateRule.json +289 -651
  411. models/mutation.policy.wanFirewall.updateSection.json +3 -9
  412. models/mutation.policy.wanNetwork.addRule.json +204 -514
  413. models/mutation.policy.wanNetwork.addSection.json +4 -12
  414. models/mutation.policy.wanNetwork.createPolicyRevision.json +3 -9
  415. models/mutation.policy.wanNetwork.discardPolicyRevision.json +2 -6
  416. models/mutation.policy.wanNetwork.moveRule.json +4 -12
  417. models/mutation.policy.wanNetwork.moveSection.json +4 -12
  418. models/mutation.policy.wanNetwork.publishPolicyRevision.json +3 -9
  419. models/mutation.policy.wanNetwork.removeRule.json +2 -6
  420. models/mutation.policy.wanNetwork.removeSection.json +2 -6
  421. models/mutation.policy.wanNetwork.updatePolicy.json +2 -6
  422. models/mutation.policy.wanNetwork.updateRule.json +203 -511
  423. models/mutation.policy.wanNetwork.updateSection.json +3 -9
  424. models/mutation.sandbox.deleteReport.json +1 -3
  425. models/mutation.sandbox.uploadFile.json +1 -3
  426. models/mutation.site.addBgpPeer.json +48 -123
  427. models/mutation.site.addCloudInterconnectPhysicalConnection.json +12 -36
  428. models/mutation.site.addCloudInterconnectSite.json +8 -24
  429. models/mutation.site.addIpsecIkeV2Site.json +10 -30
  430. models/mutation.site.addIpsecIkeV2SiteTunnels.json +14 -40
  431. models/mutation.site.addNetworkRange.json +15 -43
  432. models/mutation.site.addSecondaryAwsVSocket.json +5 -15
  433. models/mutation.site.addSecondaryAzureVSocket.json +4 -12
  434. models/mutation.site.addSocketAddOnCard.json +4 -12
  435. models/mutation.site.addSocketSite.json +12 -36
  436. models/mutation.site.addStaticHost.json +4 -10
  437. models/mutation.site.assignSiteBwLicense.json +120 -11782
  438. models/mutation.site.removeBgpPeer.json +1 -3
  439. models/mutation.site.removeCloudInterconnectPhysicalConnection.json +1 -3
  440. models/mutation.site.removeIpsecIkeV2SiteTunnels.json +2 -4
  441. models/mutation.site.removeNetworkRange.json +1 -1
  442. models/mutation.site.removeSecondaryAwsVSocket.json +1 -1
  443. models/mutation.site.removeSecondaryAzureVSocket.json +1 -1
  444. models/mutation.site.removeSite.json +1 -1
  445. models/mutation.site.removeSiteBwLicense.json +119 -11779
  446. models/mutation.site.removeSocketAddOnCard.json +3 -9
  447. models/mutation.site.removeStaticHost.json +1 -1
  448. models/mutation.site.replaceSiteBwLicense.json +121 -11785
  449. models/mutation.site.startSiteUpgrade.json +3 -9
  450. models/mutation.site.updateBgpPeer.json +47 -120
  451. models/mutation.site.updateCloudInterconnectPhysicalConnection.json +10 -30
  452. models/mutation.site.updateHa.json +4 -10
  453. models/mutation.site.updateIpsecIkeV2SiteGeneralDetails.json +11 -24
  454. models/mutation.site.updateIpsecIkeV2SiteTunnels.json +15 -43
  455. models/mutation.site.updateNetworkRange.json +15 -43
  456. models/mutation.site.updateSecondaryAwsVSocket.json +4 -12
  457. models/mutation.site.updateSecondaryAzureVSocket.json +3 -9
  458. models/mutation.site.updateSiteBwLicense.json +120 -11782
  459. models/mutation.site.updateSiteGeneralDetails.json +14 -40
  460. models/mutation.site.updateSocketInterface.json +26 -74
  461. models/mutation.site.updateStaticHost.json +4 -10
  462. models/mutation.sites.addBgpPeer.json +48 -123
  463. models/mutation.sites.addCloudInterconnectPhysicalConnection.json +12 -36
  464. models/mutation.sites.addCloudInterconnectSite.json +8 -24
  465. models/mutation.sites.addIpsecIkeV2Site.json +10 -30
  466. models/mutation.sites.addIpsecIkeV2SiteTunnels.json +14 -40
  467. models/mutation.sites.addNetworkRange.json +15 -43
  468. models/mutation.sites.addSecondaryAwsVSocket.json +5 -15
  469. models/mutation.sites.addSecondaryAzureVSocket.json +4 -12
  470. models/mutation.sites.addSocketAddOnCard.json +4 -12
  471. models/mutation.sites.addSocketSite.json +12 -36
  472. models/mutation.sites.addStaticHost.json +4 -10
  473. models/mutation.sites.assignSiteBwLicense.json +120 -11782
  474. models/mutation.sites.removeBgpPeer.json +1 -3
  475. models/mutation.sites.removeCloudInterconnectPhysicalConnection.json +1 -3
  476. models/mutation.sites.removeIpsecIkeV2SiteTunnels.json +2 -4
  477. models/mutation.sites.removeNetworkRange.json +1 -1
  478. models/mutation.sites.removeSecondaryAwsVSocket.json +1 -1
  479. models/mutation.sites.removeSecondaryAzureVSocket.json +1 -1
  480. models/mutation.sites.removeSite.json +1 -1
  481. models/mutation.sites.removeSiteBwLicense.json +119 -11779
  482. models/mutation.sites.removeSocketAddOnCard.json +3 -9
  483. models/mutation.sites.removeStaticHost.json +1 -1
  484. models/mutation.sites.replaceSiteBwLicense.json +121 -11785
  485. models/mutation.sites.startSiteUpgrade.json +3 -9
  486. models/mutation.sites.updateBgpPeer.json +47 -120
  487. models/mutation.sites.updateCloudInterconnectPhysicalConnection.json +10 -30
  488. models/mutation.sites.updateHa.json +4 -10
  489. models/mutation.sites.updateIpsecIkeV2SiteGeneralDetails.json +11 -24
  490. models/mutation.sites.updateIpsecIkeV2SiteTunnels.json +15 -43
  491. models/mutation.sites.updateNetworkRange.json +15 -43
  492. models/mutation.sites.updateSecondaryAwsVSocket.json +4 -12
  493. models/mutation.sites.updateSecondaryAzureVSocket.json +3 -9
  494. models/mutation.sites.updateSiteBwLicense.json +120 -11782
  495. models/mutation.sites.updateSiteGeneralDetails.json +14 -40
  496. models/mutation.sites.updateSocketInterface.json +26 -74
  497. models/mutation.sites.updateStaticHost.json +4 -10
  498. models/mutation.xdr.addStoryComment.json +15 -113
  499. models/mutation.xdr.analystFeedback.json +229 -26307
  500. models/mutation.xdr.deleteStoryComment.json +15 -113
  501. models/query.accountBySubdomain.json +2 -1
  502. models/query.accountMetrics.json +15 -12
  503. models/query.accountRoles.json +1 -1
  504. models/query.accountSnapshot.json +4 -2
  505. models/query.admin.json +1 -1
  506. models/query.admins.json +7 -10
  507. models/query.appStats.json +633 -33
  508. models/query.appStatsTimeSeries.json +15 -28
  509. models/query.auditFeed.json +225 -16
  510. models/query.catalogs.json +106 -215
  511. models/query.container.json +21 -471
  512. models/query.devices.json +333 -620
  513. models/query.enterpriseDirectory.json +3596 -0
  514. models/query.entityLookup.json +15 -27
  515. models/query.events.json +1875 -33
  516. models/query.eventsFeed.json +30 -14
  517. models/query.eventsTimeSeries.json +15 -28
  518. models/query.groups.group.members.json +23 -55
  519. models/query.groups.groupList.json +87 -266
  520. models/query.groups.whereUsed.json +2 -6
  521. models/query.hardware.json +92 -192
  522. models/query.hardwareManagement.json +21 -63
  523. models/query.licensing.json +76 -11721
  524. models/query.policy.appTenantRestriction.policy.json +2 -6
  525. models/query.policy.dynamicIpAllocation.policy.json +2 -6
  526. models/query.policy.internetFirewall.policy.json +2 -6
  527. models/query.policy.remotePortFwd.policy.json +2 -6
  528. models/query.policy.socketLan.policy.json +2 -6
  529. models/query.policy.terminalServer.policy.json +2 -6
  530. models/query.policy.wanFirewall.policy.json +2 -6
  531. models/query.policy.wanNetwork.policy.json +2 -6
  532. models/query.popLocations.json +34 -74
  533. models/query.sandbox.json +44 -83
  534. models/query.servicePrincipalAdmin.json +1098 -0
  535. models/query.site.availableVersionList.json +4 -5
  536. models/query.site.bgpPeer.json +2 -6
  537. models/query.site.bgpPeerList.json +2 -6
  538. models/query.site.cloudInterconnectConnectionConnectivity.json +1 -3
  539. models/query.site.cloudInterconnectPhysicalConnection.json +1 -3
  540. models/query.site.cloudInterconnectPhysicalConnectionId.json +3 -9
  541. models/query.site.secondaryAwsVSocket.json +1 -1
  542. models/query.site.secondaryAzureVSocket.json +1 -1
  543. models/query.site.siteBgpStatus.json +2 -6
  544. models/query.socketPortMetrics.json +465 -33
  545. models/query.socketPortMetricsTimeSeries.json +15 -28
  546. models/query.subDomains.json +1 -1
  547. models/query.xdr.stories.json +306 -26440
  548. models/query.xdr.story.json +40 -26100
  549. schema/catolib.py +1255 -972
  550. schema/importSchema.py +53 -48
  551. catocli/parsers/mutation/README.md +0 -4
  552. catocli/parsers/mutation_accountManagement_disableAccount/README.md +0 -16
  553. catocli/parsers/parserApiClient.py +0 -948
  554. catocli/parsers/query/README.md +0 -3
  555. catocli-2.1.1.dist-info/RECORD +0 -645
  556. models/mutation.accountManagement.disableAccount.json +0 -545
  557. models/query.policy.json +0 -40899
  558. models/query.site.json +0 -5688
  559. schema/remove_policyid.py +0 -89
  560. schema/remove_policyid_mutations.py +0 -89
  561. {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/WHEEL +0 -0
  562. {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/entry_points.txt +0 -0
  563. {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/licenses/LICENSE +0 -0
  564. {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1195 @@
1
+ """
2
+ Custom Parser API Client for Cato CLI
3
+
4
+ This module provides enhanced GraphQL query generation and API request handling
5
+ for the Cato Networks CLI tool. It includes improved field expansion logic,
6
+ better error handling, and support for custom query templates.
7
+
8
+ Key improvements over the original:
9
+ - Enhanced renderArgsAndFields function with better field expansion
10
+ - Improved error handling and validation
11
+ - Support for custom query templates and field overrides
12
+ - Better handling of nested field structures
13
+ - Enhanced debugging capabilities
14
+ """
15
+
16
+ import codecs
17
+ import json
18
+ import os
19
+ import sys
20
+ from graphql_client import ApiClient, CallApi
21
+ from graphql_client.api_client import ApiException
22
+ import logging
23
+ import pprint
24
+ import uuid
25
+ import string
26
+ from urllib3.filepost import encode_multipart_formdata
27
+
28
+
29
+ class CustomAPIClient:
30
+ """Enhanced API Client with custom query generation capabilities"""
31
+
32
+ def __init__(self):
33
+ self.custom_field_mappings = {
34
+ # Define custom field expansions for specific operations
35
+ "query.appStats": {
36
+ "records": [
37
+ "fieldsUnitTypes",
38
+ "fieldsMap",
39
+ "trends",
40
+ "prevTimeFrame",
41
+ "flatFields"
42
+ ]
43
+ }
44
+ }
45
+
46
+ def get_custom_fields(self, operation_name, field_name):
47
+ """Get custom field expansions for a specific operation and field"""
48
+ if operation_name in self.custom_field_mappings:
49
+ return self.custom_field_mappings[operation_name].get(field_name, [])
50
+ return []
51
+
52
+
53
+ # Global instance for field mappings
54
+ custom_client = CustomAPIClient()
55
+
56
+
57
+ def createRequest(args, configuration):
58
+ """
59
+ Enhanced request creation with improved error handling and validation
60
+
61
+ Args:
62
+ args: Command line arguments
63
+ configuration: API configuration object
64
+
65
+ Returns:
66
+ API response or error object
67
+ """
68
+ params = vars(args)
69
+ instance = CallApi(ApiClient(configuration))
70
+ operation_name = params["operation_name"]
71
+
72
+ try:
73
+ operation = loadJSON(f"models/{operation_name}.json")
74
+ except Exception as e:
75
+ print(f"ERROR: Failed to load operation model for {operation_name}: {e}")
76
+ return None
77
+
78
+ variables_obj = {}
79
+
80
+ # Parse JSON input with better error handling (including for -t flag)
81
+ if params["json"]:
82
+ try:
83
+ variables_obj = json.loads(params["json"])
84
+ if not isinstance(variables_obj, dict):
85
+ print("ERROR: JSON input must be an object/dictionary")
86
+ return None
87
+ except ValueError as e:
88
+ print(f"ERROR: Invalid JSON syntax: {e}")
89
+ print("Example: '{\"yourKey\":\"yourValue\"}'")
90
+ return None
91
+ except Exception as e:
92
+ print(f"ERROR: Unexpected error parsing JSON: {e}")
93
+ return None
94
+ else:
95
+ # Default to empty object if no json provided
96
+ variables_obj = {}
97
+
98
+ # Handle account ID for different operation types
99
+ if operation_name in ["query.eventsFeed", "query.auditFeed"]:
100
+ # Only add accountIDs if not already provided in JSON
101
+ if "accountIDs" not in variables_obj:
102
+ variables_obj["accountIDs"] = [configuration.accountID]
103
+ elif "accountId" in operation.get("args", {}):
104
+ variables_obj["accountId"] = configuration.accountID
105
+ else:
106
+ variables_obj["accountID"] = configuration.accountID
107
+
108
+ # Validation logic
109
+ if params["t"]:
110
+ # Skip validation when using -t flag
111
+ is_ok = True
112
+ else:
113
+ is_ok, invalid_vars, message = validateArgs(variables_obj, operation)
114
+
115
+ if is_ok:
116
+ body = generateGraphqlPayload(variables_obj, operation, operation_name)
117
+
118
+ if params["t"]:
119
+ # Use dynamically generated query with custom field mappings
120
+ print(body["query"])
121
+ return None
122
+ else:
123
+ try:
124
+ return instance.call_api(body, params)
125
+ except ApiException as e:
126
+ return e
127
+ else:
128
+ print(f"ERROR: {message}, {', '.join(invalid_vars)}")
129
+ try:
130
+ query_payload_file = f"queryPayloads/{operation_name}.json"
131
+ query_payload = loadJSON(query_payload_file)
132
+ print(f"\nExample: catocli {operation_name.replace('.', ' ')} {json.dumps(query_payload['variables'])}")
133
+ except Exception as e:
134
+ print(f"ERROR: Could not load query example: {e}")
135
+
136
+
137
+ def querySiteLocation(args, configuration):
138
+ """
139
+ Enhanced site location query with better validation
140
+ """
141
+ params = vars(args)
142
+ operation_name = params["operation_name"]
143
+
144
+ # Load the site location data (not the model definition)
145
+ try:
146
+ site_data = loadJSON(f"schema/{operation_name}.json")
147
+ except Exception as e:
148
+ print(f"ERROR: Failed to load site location data: {e}")
149
+ return None
150
+
151
+ try:
152
+ variables_obj = json.loads(params["json"])
153
+ except ValueError as e:
154
+ print(f"ERROR: Invalid JSON syntax: {e}")
155
+ print("Example: '{\"filters\":[{\"search\": \"Your city here\",\"field\":\"city\",\"operation\":\"exact\"}]}'")
156
+ return None
157
+
158
+ # Validate filters structure
159
+ if not variables_obj.get("filters"):
160
+ print("ERROR: Missing 'filters' array in request")
161
+ print("Example: '{\"filters\":[{\"search\": \"Your city here\",\"field\":\"city\",\"operation\":\"exact\"}]}'")
162
+ return None
163
+
164
+ if not isinstance(variables_obj.get("filters"), list):
165
+ print("ERROR: 'filters' must be an array")
166
+ return None
167
+
168
+ # Validate each filter
169
+ required_fields = ["search", "field", "operation"]
170
+ valid_fields = ['countryName', 'stateName', 'city']
171
+ valid_operations = ['startsWith', 'endsWith', 'exact', 'contains']
172
+
173
+ for i, filter_obj in enumerate(variables_obj["filters"]):
174
+ if not isinstance(filter_obj, dict):
175
+ print(f"ERROR: Filter {i} must be an object with 'search', 'field', and 'operation' properties")
176
+ return None
177
+
178
+ # Check required fields
179
+ for field in required_fields:
180
+ if field not in filter_obj:
181
+ print(f"ERROR: Filter {i} missing required field '{field}'")
182
+ return None
183
+
184
+ # Validate field values
185
+ search = filter_obj.get("search")
186
+ field = filter_obj.get("field")
187
+ operation = filter_obj.get("operation")
188
+
189
+ if not isinstance(search, str) or len(search) < 3:
190
+ print(f"ERROR: Filter {i} 'search' must be a string with at least 3 characters")
191
+ return None
192
+
193
+ if field not in valid_fields:
194
+ print(f"ERROR: Filter {i} 'field' must be one of: {', '.join(valid_fields)}")
195
+ return None
196
+
197
+ if operation not in valid_operations:
198
+ print(f"ERROR: Filter {i} 'operation' must be one of: {', '.join(valid_operations)}")
199
+ return None
200
+
201
+ # Process results using the site location data
202
+ response = {"data": []}
203
+
204
+ # Search through the site location data
205
+ for key, site_obj in site_data.items():
206
+ is_match = True
207
+ for filter_obj in variables_obj["filters"]:
208
+ search = filter_obj.get("search")
209
+ field = filter_obj.get("field")
210
+ operation_type = filter_obj.get("operation")
211
+
212
+ if field in site_obj:
213
+ field_value = str(site_obj[field])
214
+ if operation_type == "startsWith" and not field_value.startswith(search):
215
+ is_match = False
216
+ break
217
+ elif operation_type == "endsWith" and not field_value.endswith(search):
218
+ is_match = False
219
+ break
220
+ elif operation_type == "exact" and field_value != search:
221
+ is_match = False
222
+ break
223
+ elif operation_type == "contains" and search not in field_value:
224
+ is_match = False
225
+ break
226
+ else:
227
+ is_match = False
228
+ break
229
+
230
+ if is_match:
231
+ response["data"].append(site_obj)
232
+
233
+ # Return response in the format expected by CLI driver (as a list)
234
+ # The CLI driver expects response[0] to contain the actual data
235
+ return [response]
236
+
237
+
238
+ def createRawRequest(args, configuration):
239
+ """
240
+ Enhanced raw request handling with better error reporting
241
+ """
242
+ params = vars(args)
243
+
244
+ # Handle endpoint override
245
+ if hasattr(args, 'endpoint') and args.endpoint:
246
+ configuration.host = args.endpoint
247
+
248
+ # Check if binary/multipart mode is enabled
249
+ if hasattr(args, 'binary') and args.binary:
250
+ return createRawBinaryRequest(args, configuration)
251
+
252
+ instance = CallApi(ApiClient(configuration))
253
+
254
+ try:
255
+ body = json.loads(params["json"])
256
+
257
+ # Validate GraphQL request structure
258
+ if not isinstance(body, dict):
259
+ print("ERROR: Request must be a JSON object")
260
+ return None
261
+
262
+ if "query" not in body:
263
+ print("ERROR: Request must contain a 'query' field")
264
+ return None
265
+
266
+ except ValueError as e:
267
+ print(f"ERROR: Invalid JSON syntax: {e}")
268
+ return None
269
+ except Exception as e:
270
+ print(f"ERROR: Unexpected error parsing request: {e}")
271
+ return None
272
+
273
+ if params["t"]:
274
+ if params["p"]:
275
+ print(json.dumps(body, indent=2, sort_keys=True).replace("\\n", "\n").replace("\\t", "\t"))
276
+ else:
277
+ print(json.dumps(body).replace("\\n", " ").replace("\\t", " ").replace(" ", " ").replace(" ", " "))
278
+ return None
279
+ else:
280
+ try:
281
+ return instance.call_api(body, params)
282
+ except ApiException as e:
283
+ print(f"ERROR: API request failed: {e}")
284
+ return None
285
+
286
+
287
+ def generateGraphqlPayload(variables_obj, operation, operation_name):
288
+ """
289
+ Enhanced GraphQL payload generation with improved field handling
290
+
291
+ Args:
292
+ variables_obj: Variables for the GraphQL query
293
+ operation: Operation definition from schema
294
+ operation_name: Name of the operation (e.g., 'query.appStats')
295
+
296
+ Returns:
297
+ Complete GraphQL request payload
298
+ """
299
+ indent = "\t"
300
+ query_str = ""
301
+ variable_str = ""
302
+
303
+ # Generate variable declarations
304
+ for var_name in variables_obj:
305
+ if var_name in operation["operationArgs"]:
306
+ variable_str += operation["operationArgs"][var_name]["requestStr"]
307
+
308
+ # Build query structure
309
+ operation_ary = operation_name.split(".")
310
+ operation_type = operation_ary.pop(0)
311
+ query_str = f"{operation_type} "
312
+ query_str += renderCamelCase(".".join(operation_ary))
313
+ query_str += f" ( {variable_str}) {{\n"
314
+ query_str += f"{indent}{operation['name']} ( "
315
+
316
+ # Add operation arguments
317
+ for arg_name in operation["args"]:
318
+ arg = operation["args"][arg_name]
319
+ if arg["varName"] in variables_obj:
320
+ query_str += arg["responseStr"]
321
+
322
+ # Generate field selection with enhanced rendering
323
+ query_str += ") {\n" + renderArgsAndFields("", variables_obj, operation, operation["type"]["definition"], operation_name, "\t\t") + "\t}"
324
+ query_str += f"{indent}\n}}"
325
+
326
+ body = {
327
+ "query": query_str,
328
+ "variables": variables_obj,
329
+ "operationName": renderCamelCase(".".join(operation_ary)),
330
+ }
331
+ return body
332
+
333
+
334
+ def get_help(path):
335
+ """
336
+ Enhanced help generation with better error handling
337
+ Stop including catocli examples after "Advanced Usage" section
338
+ Add dynamic GitHub link if advanced examples exist
339
+ """
340
+ match_cmd = f"catocli {path.replace('_', ' ')}"
341
+ pwd = os.path.dirname(__file__)
342
+ doc = f"{path}/README.md"
343
+ abs_path = os.path.join(pwd, doc)
344
+ new_line = "\nEXAMPLES:\n"
345
+
346
+ # Check if advanced examples exist by looking for example file in schema directory
347
+ # Convert path format (e.g., query_appStats -> query.appStats)
348
+ operation_name = path.replace('_', '.', 1) # Only replace first underscore
349
+ schema_dir = os.path.dirname(os.path.dirname(pwd)) # Go up two levels to get to root
350
+ example_file_path = os.path.join(schema_dir, "schema", "examples", f"{operation_name}.md")
351
+ has_advanced_examples = os.path.exists(example_file_path)
352
+
353
+ try:
354
+ with open(abs_path, "r") as f:
355
+ lines = f.readlines()
356
+
357
+ # Flag to stop processing after Advanced Usage section
358
+ stop_after_advanced = False
359
+
360
+ for line in lines:
361
+ # Check if we've hit the Advanced Usage section
362
+ if "Advanced Usage" in line or "## Advanced" in line:
363
+ stop_after_advanced = True
364
+ continue
365
+
366
+ # Skip catocli examples after Advanced Usage
367
+ if stop_after_advanced and match_cmd in line:
368
+ continue
369
+
370
+ # Include catocli examples before Advanced Usage
371
+ if match_cmd in line:
372
+ clean_line = line.replace("<br /><br />", "").replace("`", "")
373
+ new_line += f"{clean_line}\n"
374
+
375
+ # Add GitHub link if advanced examples exist
376
+ if has_advanced_examples:
377
+ new_line += f"\nPlease see advanced usage examples at the following:\n"
378
+ new_line += f"https://github.com/catonetworks/cato-cli/tree/main/catocli/parsers/{path}\n"
379
+
380
+ except FileNotFoundError:
381
+ new_line += f"No examples found for {match_cmd}\n"
382
+ # Still add GitHub link if advanced examples exist
383
+ if has_advanced_examples:
384
+ new_line += f"\nPlease see advanced usage examples at the following:\n"
385
+ new_line += f"https://github.com/catonetworks/cato-cli/tree/main/catocli/parsers/{path}\n"
386
+ except Exception as e:
387
+ new_line += f"Error loading help: {e}\n"
388
+
389
+ return new_line
390
+
391
+
392
+ def validateArgs(variables_obj, operation):
393
+ """
394
+ Enhanced argument validation with detailed error reporting
395
+ """
396
+ is_ok = True
397
+ invalid_vars = []
398
+ message = "Arguments are missing or have invalid values: "
399
+
400
+ # Check for invalid variable names
401
+ operation_args = operation.get("operationArgs", {})
402
+ for var_name in variables_obj:
403
+ if var_name not in operation_args:
404
+ is_ok = False
405
+ invalid_vars.append(f'"{var_name}"')
406
+ message = f"Invalid argument names. Expected: {', '.join(list(operation_args.keys()))}"
407
+
408
+ # Check for missing required variables
409
+ if is_ok:
410
+ for var_name, arg_info in operation_args.items():
411
+ if arg_info.get("required", False) and var_name not in variables_obj:
412
+ is_ok = False
413
+ invalid_vars.append(f'"{var_name}"')
414
+ elif var_name in variables_obj:
415
+ value = variables_obj[var_name]
416
+ if arg_info.get("required", False) and (value == "" or value is None):
417
+ is_ok = False
418
+ invalid_vars.append(f'"{var_name}":"{str(value)}"')
419
+
420
+ return is_ok, invalid_vars, message
421
+
422
+
423
+ def loadJSON(file):
424
+ """
425
+ Enhanced JSON loading with better error handling and path resolution
426
+ """
427
+ module_dir = os.path.dirname(__file__)
428
+ # Navigate up two directory levels (from parsers/ to catocli/ to root)
429
+ module_dir = os.path.dirname(module_dir) # Go up from parsers/
430
+ module_dir = os.path.dirname(module_dir) # Go up from catocli/
431
+
432
+ try:
433
+ file_path = os.path.join(module_dir, file)
434
+ with open(file_path, 'r') as data:
435
+ config = json.load(data)
436
+ return config
437
+ except FileNotFoundError:
438
+ logging.error(f"File \"{os.path.join(module_dir, file)}\" not found.")
439
+ raise
440
+ except json.JSONDecodeError as e:
441
+ logging.error(f"Invalid JSON in file \"{os.path.join(module_dir, file)}\": {e}")
442
+ raise
443
+ except Exception as e:
444
+ logging.error(f"Error loading file \"{os.path.join(module_dir, file)}\": {e}")
445
+ raise
446
+
447
+
448
+ def renderCamelCase(path_str):
449
+ """
450
+ Convert dot-separated path to camelCase
451
+
452
+ Args:
453
+ path_str: Dot-separated string like 'app.stats'
454
+
455
+ Returns:
456
+ camelCase string like 'appStats'
457
+ """
458
+ if not path_str:
459
+ return ""
460
+
461
+ result = ""
462
+ path_ary = path_str.split(".")
463
+
464
+ for i, path in enumerate(path_ary):
465
+ if not path: # Skip empty parts
466
+ continue
467
+
468
+ if i == 0:
469
+ result += path[0].lower() + path[1:] if len(path) > 1 else path.lower()
470
+ else:
471
+ result += path[0].upper() + path[1:] if len(path) > 1 else path.upper()
472
+
473
+ return result
474
+
475
+
476
+ def renderArgsAndFields(response_arg_str, variables_obj, cur_operation, definition, operation_name, indent):
477
+ """
478
+ ENHANCED field rendering with custom field expansion support
479
+
480
+ This is the key function that generates the GraphQL field selection.
481
+ It now includes support for custom field expansions defined in custom_field_mappings.
482
+
483
+ Args:
484
+ response_arg_str: Current field string being built
485
+ variables_obj: Variables for the query
486
+ cur_operation: Current operation definition
487
+ definition: Field definitions
488
+ operation_name: Name of the operation (for custom mappings)
489
+ indent: Current indentation level
490
+
491
+ Returns:
492
+ Complete field selection string
493
+ """
494
+ if not definition or not isinstance(definition, dict) or 'fields' not in definition:
495
+ return response_arg_str
496
+
497
+ # DEBUG: Print fields for debugging
498
+ # print(f"DEBUG: renderArgsAndFields - operation_name={operation_name}, fields={list(definition['fields'].keys())}", file=sys.stderr)
499
+
500
+ for field_name in definition['fields']:
501
+ field = definition['fields'][field_name]
502
+ field_display_name = field.get('alias', field['name'])
503
+
504
+ # Check if field has arguments and whether they are present in variables
505
+ should_include_field = True
506
+ args_present = False
507
+ arg_str = ""
508
+
509
+ if field.get("args") and not isinstance(field['args'], list):
510
+ if len(list(field['args'].keys())) > 0:
511
+ # Field has arguments - check if any are required or present
512
+ arg_str = " ( "
513
+ required_args_missing = False
514
+
515
+ for arg_name in field['args']:
516
+ arg = field['args'][arg_name]
517
+ if arg["varName"] in variables_obj:
518
+ arg_str += arg['responseStr'] + " "
519
+ args_present = True
520
+ elif arg.get("required", False):
521
+ # Required argument is missing
522
+ required_args_missing = True
523
+ break
524
+
525
+ arg_str += ") "
526
+
527
+ # Only exclude field if required arguments are missing
528
+ # If all arguments are optional, include the field even without arguments
529
+ should_include_field = not required_args_missing
530
+
531
+ # Only process field if we should include it
532
+ if should_include_field:
533
+ response_arg_str += f"{indent}{field_display_name}"
534
+ if args_present:
535
+ response_arg_str += arg_str
536
+
537
+ # ENHANCED: Check for custom field expansions first
538
+ custom_fields = custom_client.get_custom_fields(operation_name, field['name'])
539
+ if should_include_field and custom_fields:
540
+ response_arg_str += " {\n"
541
+ for custom_field in custom_fields:
542
+ response_arg_str += f"{indent}\t{custom_field}\n"
543
+ response_arg_str += f"{indent}}}\n"
544
+
545
+ # Standard nested field processing (only if no custom fields defined)
546
+ elif should_include_field and field.get("type") and field['type'].get('definition') and field['type']['definition']['fields'] is not None:
547
+ response_arg_str += " {\n"
548
+ for subfield_index in field['type']['definition']['fields']:
549
+ subfield = field['type']['definition']['fields'][subfield_index]
550
+ subfield_name = subfield.get('alias', subfield['name'])
551
+ response_arg_str += f"{indent}\t{subfield_name}"
552
+
553
+ if subfield.get("args") and len(list(subfield["args"].keys())) > 0:
554
+ sub_args_present = False
555
+ sub_arg_str = " ( "
556
+ for arg_name in subfield['args']:
557
+ arg = subfield['args'][arg_name]
558
+ if arg["varName"] in variables_obj:
559
+ sub_args_present = True
560
+ sub_arg_str += arg['responseStr'] + " "
561
+ sub_arg_str += " )"
562
+ if sub_args_present:
563
+ response_arg_str += sub_arg_str
564
+
565
+ if subfield.get("type") and subfield['type'].get("definition") and (subfield['type']['definition'].get("fields") or subfield['type']['definition'].get('inputFields')):
566
+ response_arg_str += " {\n"
567
+ response_arg_str = renderArgsAndFields(response_arg_str, variables_obj, cur_operation, subfield['type']['definition'], operation_name, indent + "\t\t")
568
+ if subfield['type']['definition'].get('possibleTypes'):
569
+ for possible_type_name in subfield['type']['definition']['possibleTypes']:
570
+ possible_type = subfield['type']['definition']['possibleTypes'][possible_type_name]
571
+ response_arg_str += f"{indent}\t\t... on {possible_type['name']} {{\n"
572
+ if possible_type.get('fields') or possible_type.get('inputFields'):
573
+ response_arg_str = renderArgsAndFields(response_arg_str, variables_obj, cur_operation, possible_type, operation_name, indent + "\t\t\t")
574
+ response_arg_str += f"{indent}\t\t}}\n"
575
+ response_arg_str += f"{indent}\t}}"
576
+ elif subfield.get('type') and subfield['type'].get('definition') and subfield['type']['definition'].get('possibleTypes'):
577
+ response_arg_str += " {\n"
578
+ response_arg_str += f"{indent}\t\t__typename\n"
579
+ for possible_type_name in subfield['type']['definition']['possibleTypes']:
580
+ possible_type = subfield['type']['definition']['possibleTypes'][possible_type_name]
581
+ response_arg_str += f"{indent}\t\t... on {possible_type['name']} {{\n"
582
+ if possible_type.get('fields') or possible_type.get('inputFields'):
583
+ response_arg_str = renderArgsAndFields(response_arg_str, variables_obj, cur_operation, possible_type, operation_name, indent + "\t\t\t")
584
+ response_arg_str += f"{indent}\t\t}}\n"
585
+ response_arg_str += f"{indent}\t}}\n"
586
+ response_arg_str += "\n"
587
+
588
+ if field['type']['definition'].get('possibleTypes'):
589
+ for possible_type_name in field['type']['definition']['possibleTypes']:
590
+ possible_type = field['type']['definition']['possibleTypes'][possible_type_name]
591
+ response_arg_str += f"{indent}\t... on {possible_type['name']} {{\n"
592
+ if possible_type.get('fields') or possible_type.get('inputFields'):
593
+ response_arg_str = renderArgsAndFields(response_arg_str, variables_obj, cur_operation, possible_type, operation_name, indent + "\t\t")
594
+ response_arg_str += f"{indent}\t}}\n"
595
+ response_arg_str += f"{indent}}}\n"
596
+
597
+ # Handle inputFields
598
+ if should_include_field and field.get('type') and field['type'].get('definition') and field['type']['definition'].get('inputFields'):
599
+ response_arg_str += " {\n"
600
+ for subfield_name in field['type']['definition'].get('inputFields'):
601
+ subfield = field['type']['definition']['inputFields'][subfield_name]
602
+ # Enhanced aliasing logic for inputFields
603
+ if (subfield.get('type') and subfield['type'].get('name') and
604
+ cur_operation.get('fieldTypes', {}).get(subfield['type']['name']) and
605
+ subfield.get('type', {}).get('kind') and
606
+ 'SCALAR' not in str(subfield['type']['kind'])):
607
+ subfield_name = f"{subfield['name']}{field['type']['definition']['name']}: {subfield['name']}"
608
+ else:
609
+ subfield_name = subfield['name']
610
+ response_arg_str += f"{indent}\t{subfield_name}"
611
+ if subfield.get('type') and subfield['type'].get('definition') and (subfield['type']['definition'].get('fields') or subfield['type']['definition'].get('inputFields')):
612
+ response_arg_str += " {\n"
613
+ response_arg_str = renderArgsAndFields(response_arg_str, variables_obj, cur_operation, subfield['type']['definition'], operation_name, indent + "\t\t")
614
+ response_arg_str += f"{indent}\t}}\n"
615
+ if field['type']['definition'].get('possibleTypes'):
616
+ for possible_type_name in field['type']['definition']['possibleTypes']:
617
+ possible_type = field['type']['definition']['possibleTypes'][possible_type_name]
618
+ response_arg_str += f"{indent}... on {possible_type['name']} {{\n"
619
+ if possible_type.get('fields') or possible_type.get('inputFields'):
620
+ response_arg_str = renderArgsAndFields(response_arg_str, variables_obj, cur_operation, possible_type, operation_name, indent + "\t\t")
621
+ response_arg_str += f"{indent}\t}}\n"
622
+ response_arg_str += f"{indent}}}\n"
623
+
624
+ if should_include_field:
625
+ response_arg_str += "\n"
626
+
627
+ return response_arg_str
628
+
629
+
630
+ # Binary/Multipart request functions (preserved from original)
631
+ def createRawBinaryRequest(args, configuration):
632
+ """Handle multipart/form-data requests for file uploads and binary content"""
633
+ params = vars(args)
634
+
635
+ # Parse the JSON body
636
+ try:
637
+ body = json.loads(params["json"])
638
+ except ValueError as e:
639
+ print(f"ERROR: JSON argument must be valid json: {e}")
640
+ return
641
+ except Exception as e:
642
+ print(f"ERROR: {e}")
643
+ return
644
+
645
+ # Build form data
646
+ form_fields = {}
647
+ files = []
648
+
649
+ # Add the operations field containing the GraphQL payload
650
+ form_fields['operations'] = json.dumps(body)
651
+
652
+ # Handle file mappings if files are specified
653
+ if hasattr(args, 'files') and args.files:
654
+ # Build the map object for file uploads
655
+ file_map = {}
656
+ for i, (field_name, file_path) in enumerate(args.files):
657
+ file_index = str(i + 1)
658
+ file_map[file_index] = [field_name]
659
+
660
+ # Read file content
661
+ try:
662
+ with open(file_path, 'rb') as f:
663
+ file_content = f.read()
664
+ files.append((file_index, (os.path.basename(file_path), file_content, 'application/octet-stream')))
665
+ except IOError as e:
666
+ print(f"ERROR: Could not read file {file_path}: {e}")
667
+ return
668
+
669
+ # Add the map field
670
+ form_fields['map'] = json.dumps(file_map)
671
+
672
+ # Test mode - just print the request structure
673
+ if params.get("t") == True:
674
+ print("Multipart form data request:")
675
+ if params.get("p") == True:
676
+ print(f"Operations: {json.dumps(json.loads(form_fields.get('operations')), indent=2)}")
677
+ else:
678
+ print(f"Operations: {form_fields.get('operations')}")
679
+ if 'map' in form_fields:
680
+ print(f"Map: {form_fields.get('map')}")
681
+ if files:
682
+ print(f"Files: {[f[0] + ': ' + f[1][0] for f in files]}")
683
+ return None
684
+
685
+ # Perform the multipart request
686
+ try:
687
+ return sendMultipartRequest(configuration, form_fields, files, params)
688
+ except Exception as e:
689
+ # Safely handle exception string conversion
690
+ try:
691
+ error_str = str(e)
692
+ except Exception:
693
+ error_str = f"Exception of type {type(e).__name__}"
694
+
695
+ if params.get("v") == True:
696
+ import traceback
697
+ print(f"ERROR: Failed to send multipart request: {error_str}")
698
+ traceback.print_exc()
699
+ else:
700
+ print(f"ERROR: Failed to send multipart request: {error_str}")
701
+ return None
702
+
703
+
704
+ # Additional helper functions for private commands and specialized operations
705
+ # (These are preserved from the original implementation)
706
+
707
+ def get_private_help(command_name, command_config):
708
+ """Generate comprehensive help text for a private command"""
709
+ usage = f"catocli private {command_name}"
710
+
711
+ # Create comprehensive JSON example with all arguments (excluding accountId)
712
+ if 'arguments' in command_config:
713
+ json_example = {}
714
+ for arg in command_config['arguments']:
715
+ arg_name = arg.get('name')
716
+ # Skip accountId since it's handled by standard -accountID CLI argument
717
+ if arg_name and arg_name.lower() != 'accountid':
718
+ if 'example' in arg:
719
+ # Use explicit example if provided
720
+ json_example[arg_name] = arg['example']
721
+ elif 'default' in arg:
722
+ # Use default value if available
723
+ json_example[arg_name] = arg['default']
724
+ else:
725
+ # Generate placeholder based on type
726
+ arg_type = arg.get('type', 'string')
727
+ if arg_type == 'string':
728
+ json_example[arg_name] = f"<{arg_name}>"
729
+ elif arg_type == 'object':
730
+ if 'struct' in arg:
731
+ # Use struct definition
732
+ json_example[arg_name] = arg['struct']
733
+ else:
734
+ json_example[arg_name] = {}
735
+ else:
736
+ json_example[arg_name] = f"<{arg_name}>"
737
+
738
+ if json_example:
739
+ # Format JSON nicely for readability in help
740
+ json_str = json.dumps(json_example, indent=2)
741
+ usage += f" '{json_str}'"
742
+
743
+ # Add common options
744
+ usage += " [-t] [-v] [-p]"
745
+
746
+ # Add command-specific arguments with descriptions (excluding accountId)
747
+ if 'arguments' in command_config:
748
+ filtered_args = [arg for arg in command_config['arguments'] if arg.get('name', '').lower() != 'accountid']
749
+ if filtered_args:
750
+ usage += "\n\nArguments:"
751
+ for arg in filtered_args:
752
+ arg_name = arg.get('name')
753
+ arg_type = arg.get('type', 'string')
754
+ arg_default = arg.get('default')
755
+ arg_example = arg.get('example')
756
+
757
+ if arg_name:
758
+ usage += f"\n --{arg_name}: {arg_type}"
759
+ if arg_default is not None:
760
+ usage += f" (default: {arg_default})"
761
+ if arg_example is not None and arg_example != arg_default:
762
+ usage += f" (example: {json.dumps(arg_example) if isinstance(arg_example, (dict, list)) else arg_example})"
763
+
764
+ # Add standard accountID information
765
+ usage += "\n\nStandard Arguments:"
766
+ usage += "\n -accountID: Account ID (taken from profile, can be overridden)"
767
+
768
+ # Add payload file info if available
769
+ if 'payloadFilePath' in command_config:
770
+ usage += f"\n\nPayload template: {command_config['payloadFilePath']}"
771
+
772
+ # Add batch processing info if configured
773
+ if 'batchSize' in command_config:
774
+ usage += f"\nBatch size: {command_config['batchSize']}"
775
+ if 'paginationParam' in command_config:
776
+ usage += f" (pagination: {command_config['paginationParam']})"
777
+
778
+ return usage
779
+
780
+
781
+ def load_payload_template(command_config):
782
+ """Load and return the GraphQL payload template for a private command"""
783
+ try:
784
+ payload_path = command_config.get('payloadFilePath')
785
+ if not payload_path:
786
+ raise ValueError("Missing payloadFilePath in command configuration")
787
+
788
+ # Construct the full path relative to the settings directory
789
+ settings_dir = os.path.expanduser("~/.cato")
790
+ full_payload_path = os.path.join(settings_dir, payload_path)
791
+
792
+ # Load the payload file using the standard JSON loading mechanism
793
+ try:
794
+ with open(full_payload_path, 'r') as f:
795
+ return json.load(f)
796
+ except FileNotFoundError:
797
+ raise ValueError(f"Payload file not found: {full_payload_path}")
798
+ except json.JSONDecodeError as e:
799
+ raise ValueError(f"Invalid JSON in payload file {full_payload_path}: {e}")
800
+ except Exception as e:
801
+ raise ValueError(f"Failed to load payload template: {e}")
802
+
803
+
804
+ def set_nested_value(obj, path, value):
805
+ """Set a value at a nested path in an object using jQuery-style JSON path syntax"""
806
+ import re
807
+
808
+ # Parse the path into components handling both dot notation and array indices
809
+ path_parts = []
810
+ for part in path.split('.'):
811
+ # Check if this part contains array notation like 'items[0]'
812
+ array_matches = re.findall(r'([^\[]+)(?:\[(\d+)\])?', part)
813
+ for match in array_matches:
814
+ key, index = match
815
+ if key: # Add the key part
816
+ path_parts.append(key)
817
+ if index: # Add the array index part
818
+ path_parts.append(int(index))
819
+
820
+ current = obj
821
+
822
+ # Navigate to the parent of the target location
823
+ for i, part in enumerate(path_parts[:-1]):
824
+ next_part = path_parts[i + 1]
825
+
826
+ if isinstance(part, int):
827
+ # Current part is an array index
828
+ if not isinstance(current, list):
829
+ raise ValueError(f"Expected array at path component {i}, got {type(current).__name__}")
830
+
831
+ # Extend array if necessary
832
+ while len(current) <= part:
833
+ current.append(None)
834
+
835
+ # Initialize the array element if it doesn't exist
836
+ if current[part] is None:
837
+ if isinstance(next_part, int):
838
+ current[part] = [] # Next part is array index, so create array
839
+ else:
840
+ current[part] = {} # Next part is object key, so create object
841
+
842
+ current = current[part]
843
+
844
+ else:
845
+ # Current part is an object key
846
+ if not isinstance(current, dict):
847
+ raise ValueError(f"Expected object at path component {i}, got {type(current).__name__}")
848
+
849
+ # Create the key if it doesn't exist
850
+ if part not in current:
851
+ if isinstance(next_part, int):
852
+ current[part] = [] # Next part is array index, so create array
853
+ else:
854
+ current[part] = {} # Next part is object key, so create object
855
+
856
+ current = current[part]
857
+
858
+ # Set the final value
859
+ final_part = path_parts[-1]
860
+ if isinstance(final_part, int):
861
+ # Final part is an array index
862
+ if not isinstance(current, list):
863
+ raise ValueError(f"Expected array at final path component, got {type(current).__name__}")
864
+
865
+ # Extend array if necessary
866
+ while len(current) <= final_part:
867
+ current.append(None)
868
+
869
+ current[final_part] = value
870
+ else:
871
+ # Final part is an object key
872
+ if not isinstance(current, dict):
873
+ raise ValueError(f"Expected object at final path component, got {type(current).__name__}")
874
+
875
+ current[final_part] = value
876
+
877
+
878
+ def apply_template_variables(template, variables, private_config):
879
+ """Apply variables to the template using path-based insertion and template replacement"""
880
+ if not template or not isinstance(template, dict):
881
+ return template
882
+
883
+ # Make a deep copy to avoid modifying the original
884
+ import copy
885
+ result = copy.deepcopy(template)
886
+
887
+ # First, handle path-based variable insertion from private_config
888
+ if private_config and 'arguments' in private_config:
889
+ for arg in private_config['arguments']:
890
+ arg_name = arg.get('name')
891
+ arg_paths = arg.get('path', [])
892
+
893
+ if arg_name and arg_name in variables and arg_paths:
894
+ # Insert the variable value at each specified path
895
+ for path in arg_paths:
896
+ try:
897
+ set_nested_value(result, path, variables[arg_name])
898
+ except Exception as e:
899
+ # If path insertion fails, continue to template replacement
900
+ pass
901
+
902
+ # Second, handle traditional template variable replacement as fallback
903
+ def traverse_and_replace(obj, path=""):
904
+ if isinstance(obj, dict):
905
+ for key, value in list(obj.items()):
906
+ new_path = f"{path}.{key}" if path else key
907
+
908
+ # Check if this is a template variable (string that starts with '{{')
909
+ if isinstance(value, str) and value.startswith('{{') and value.endswith('}}'):
910
+ # Extract variable name
911
+ var_name = value[2:-2].strip()
912
+
913
+ # Replace with actual value if available
914
+ if var_name in variables:
915
+ obj[key] = variables[var_name]
916
+
917
+ # Recursively process nested objects
918
+ else:
919
+ traverse_and_replace(value, new_path)
920
+
921
+ elif isinstance(obj, list):
922
+ for i, item in enumerate(obj):
923
+ traverse_and_replace(item, f"{path}[{i}]")
924
+
925
+ traverse_and_replace(result)
926
+ return result
927
+
928
+
929
+ def createPrivateRequest(args, configuration):
930
+ """Handle private command execution using GraphQL payload templates"""
931
+ params = vars(args)
932
+
933
+ # Get the private command configuration
934
+ private_command = params.get('private_command')
935
+ private_config = params.get('private_config')
936
+
937
+ if not private_command or not private_config:
938
+ print("ERROR: Missing private command configuration")
939
+ return None
940
+
941
+ # Load private settings and apply ONLY for private commands
942
+ try:
943
+ settings_file = os.path.expanduser("~/.cato/settings.json")
944
+ with open(settings_file, 'r') as f:
945
+ private_settings = json.load(f)
946
+ except (FileNotFoundError, json.JSONDecodeError):
947
+ private_settings = {}
948
+
949
+ # Override endpoint if specified in private settings
950
+ if 'baseUrl' in private_settings:
951
+ configuration.host = private_settings['baseUrl']
952
+
953
+ # Add custom headers from private settings
954
+ if 'headers' in private_settings and isinstance(private_settings['headers'], dict):
955
+ if not hasattr(configuration, 'custom_headers'):
956
+ configuration.custom_headers = {}
957
+ for key, value in private_settings['headers'].items():
958
+ configuration.custom_headers[key] = value
959
+
960
+ # Parse input JSON variables
961
+ try:
962
+ variables = json.loads(params.get('json', '{}'))
963
+ except ValueError as e:
964
+ print(f"ERROR: Invalid JSON input: {e}")
965
+ return None
966
+
967
+ # Apply default values from settings configuration first
968
+ for arg in private_config.get('arguments', []):
969
+ arg_name = arg.get('name')
970
+ if arg_name and 'default' in arg:
971
+ variables[arg_name] = arg['default']
972
+
973
+ # Apply profile account ID as fallback (lower priority than settings defaults)
974
+ if configuration and hasattr(configuration, 'accountID'):
975
+ if 'accountID' not in variables and 'accountId' not in variables:
976
+ variables['accountID'] = configuration.accountID
977
+ variables['accountId'] = configuration.accountID
978
+ elif 'accountID' in variables and 'accountId' not in variables:
979
+ variables['accountId'] = variables['accountID']
980
+ elif 'accountId' in variables and 'accountID' not in variables:
981
+ variables['accountID'] = variables['accountId']
982
+
983
+ # Apply CLI argument values (highest priority)
984
+ for arg in private_config.get('arguments', []):
985
+ arg_name = arg.get('name')
986
+ if arg_name:
987
+ # Handle special case for accountId
988
+ if arg_name.lower() == 'accountid':
989
+ if hasattr(args, 'accountID') and getattr(args, 'accountID') is not None:
990
+ arg_value = getattr(args, 'accountID')
991
+ variables['accountID'] = arg_value
992
+ variables['accountId'] = arg_value
993
+ elif hasattr(args, 'accountId') and getattr(args, 'accountId') is not None:
994
+ arg_value = getattr(args, 'accountId')
995
+ variables['accountID'] = arg_value
996
+ variables['accountId'] = arg_value
997
+ else:
998
+ if hasattr(args, arg_name):
999
+ arg_value = getattr(args, arg_name)
1000
+ if arg_value is not None:
1001
+ variables[arg_name] = arg_value
1002
+
1003
+ # Load the payload template
1004
+ try:
1005
+ payload_template = load_payload_template(private_config)
1006
+ except ValueError as e:
1007
+ print(f"ERROR: {e}")
1008
+ return None
1009
+
1010
+ # Apply variables to the template
1011
+ body = apply_template_variables(payload_template, variables, private_config)
1012
+
1013
+ # Test mode - just print the request
1014
+ if params.get('t'):
1015
+ if params.get('p'):
1016
+ print(json.dumps(body, indent=2, sort_keys=True))
1017
+ else:
1018
+ print(json.dumps(body))
1019
+ return None
1020
+
1021
+ # Execute the GraphQL request
1022
+ try:
1023
+ return sendPrivateGraphQLRequest(configuration, body, params)
1024
+ except Exception as e:
1025
+ return e
1026
+
1027
+
1028
+ def sendMultipartRequest(configuration, form_fields, files, params):
1029
+ """Send a multipart/form-data request directly using urllib3"""
1030
+ import urllib3
1031
+
1032
+ # Create pool manager
1033
+ pool_manager = urllib3.PoolManager(
1034
+ cert_reqs='CERT_NONE' if not getattr(configuration, 'verify_ssl', False) else 'CERT_REQUIRED'
1035
+ )
1036
+
1037
+ # Prepare form data
1038
+ fields = []
1039
+ for key, value in form_fields.items():
1040
+ fields.append((key, value))
1041
+
1042
+ for file_key, (filename, content, content_type) in files:
1043
+ fields.append((file_key, (filename, content, content_type)))
1044
+
1045
+ # Encode multipart data
1046
+ body_data, content_type = encode_multipart_formdata(fields)
1047
+
1048
+ # Prepare headers
1049
+ headers = {
1050
+ 'Content-Type': content_type,
1051
+ 'User-Agent': f"Cato-CLI-v{getattr(configuration, 'version', 'unknown')}"
1052
+ }
1053
+
1054
+ # Add API key if not using custom headers
1055
+ using_custom_headers = hasattr(configuration, 'custom_headers') and configuration.custom_headers
1056
+ if not using_custom_headers and hasattr(configuration, 'api_key') and configuration.api_key and 'x-api-key' in configuration.api_key:
1057
+ headers['x-api-key'] = configuration.api_key['x-api-key']
1058
+
1059
+ # Add custom headers
1060
+ if using_custom_headers:
1061
+ headers.update(configuration.custom_headers)
1062
+
1063
+ # Verbose output
1064
+ if params.get("v"):
1065
+ print(f"Host: {getattr(configuration, 'host', 'unknown')}")
1066
+ masked_headers = headers.copy()
1067
+ if 'x-api-key' in masked_headers:
1068
+ masked_headers['x-api-key'] = '***MASKED***'
1069
+ print(f"Request Headers: {json.dumps(masked_headers, indent=4, sort_keys=True)}")
1070
+ print(f"Content-Type: {content_type}")
1071
+ print(f"Form fields: {list(form_fields.keys())}")
1072
+ print(f"Files: {[f[0] for f in files]}\n")
1073
+
1074
+ try:
1075
+ # Make the request
1076
+ resp = pool_manager.request(
1077
+ 'POST',
1078
+ getattr(configuration, 'host', 'https://api.catonetworks.com/api/v1/graphql'),
1079
+ body=body_data,
1080
+ headers=headers
1081
+ )
1082
+
1083
+ # Parse response
1084
+ if resp.status < 200 or resp.status >= 300:
1085
+ reason = resp.reason if resp.reason is not None else "Unknown Error"
1086
+ error_msg = f"HTTP {resp.status}: {reason}"
1087
+ if resp.data:
1088
+ try:
1089
+ error_msg += f"\n{resp.data.decode('utf-8')}"
1090
+ except Exception:
1091
+ error_msg += f"\n{resp.data}"
1092
+ print(f"ERROR: {error_msg}")
1093
+ return None
1094
+
1095
+ try:
1096
+ response_data = json.loads(resp.data.decode('utf-8'))
1097
+ except json.JSONDecodeError:
1098
+ response_data = resp.data.decode('utf-8')
1099
+
1100
+ return [response_data]
1101
+
1102
+ except Exception as e:
1103
+ # Safely handle exception string conversion
1104
+ try:
1105
+ error_str = str(e)
1106
+ except Exception:
1107
+ error_str = f"Exception of type {type(e).__name__}"
1108
+ print(f"ERROR: Network/request error: {error_str}")
1109
+ return None
1110
+
1111
+
1112
+ def sendPrivateGraphQLRequest(configuration, body, params):
1113
+ """Send a GraphQL request for private commands without User-Agent header"""
1114
+ import urllib3
1115
+
1116
+ # Create pool manager
1117
+ pool_manager = urllib3.PoolManager(
1118
+ cert_reqs='CERT_NONE' if not getattr(configuration, 'verify_ssl', False) else 'CERT_REQUIRED'
1119
+ )
1120
+
1121
+ # Prepare headers WITHOUT User-Agent
1122
+ headers = {
1123
+ 'Content-Type': 'application/json'
1124
+ }
1125
+
1126
+ # Add API key if not using custom headers
1127
+ using_custom_headers = hasattr(configuration, 'custom_headers') and configuration.custom_headers
1128
+ if not using_custom_headers and hasattr(configuration, 'api_key') and configuration.api_key and 'x-api-key' in configuration.api_key:
1129
+ headers['x-api-key'] = configuration.api_key['x-api-key']
1130
+
1131
+ # Add custom headers
1132
+ if using_custom_headers:
1133
+ headers.update(configuration.custom_headers)
1134
+
1135
+ # Encode headers to handle Unicode characters properly
1136
+ encoded_headers = {}
1137
+ for key, value in headers.items():
1138
+ # Ensure header values are properly encoded as strings
1139
+ if isinstance(value, str):
1140
+ # Replace problematic Unicode characters that can't be encoded in latin-1
1141
+ value = value.encode('utf-8', errors='replace').decode('latin-1', errors='replace')
1142
+ encoded_headers[key] = value
1143
+ headers = encoded_headers
1144
+
1145
+ # Verbose output
1146
+ if params.get("v"):
1147
+ print(f"Host: {getattr(configuration, 'host', 'unknown')}")
1148
+ masked_headers = headers.copy()
1149
+ if 'x-api-key' in masked_headers:
1150
+ masked_headers['x-api-key'] = '***MASKED***'
1151
+ if 'Cookie' in masked_headers:
1152
+ masked_headers['Cookie'] = '***MASKED***'
1153
+ print(f"Request Headers: {json.dumps(masked_headers, indent=4, sort_keys=True)}")
1154
+ print(f"Request Data: {json.dumps(body, indent=4, sort_keys=True)}\n")
1155
+
1156
+ # Prepare request body
1157
+ body_data = json.dumps(body).encode('utf-8')
1158
+
1159
+ try:
1160
+ # Make the request
1161
+ resp = pool_manager.request(
1162
+ 'POST',
1163
+ getattr(configuration, 'host', 'https://api.catonetworks.com/api/v1/graphql'),
1164
+ body=body_data,
1165
+ headers=headers
1166
+ )
1167
+
1168
+ # Parse response
1169
+ if resp.status < 200 or resp.status >= 300:
1170
+ reason = resp.reason if resp.reason is not None else "Unknown Error"
1171
+ error_msg = f"HTTP {resp.status}: {reason}"
1172
+ if resp.data:
1173
+ try:
1174
+ error_msg += f"\n{resp.data.decode('utf-8')}"
1175
+ except Exception:
1176
+ error_msg += f"\n{resp.data}"
1177
+ print(f"ERROR: {error_msg}")
1178
+ return None
1179
+
1180
+ try:
1181
+ response_data = json.loads(resp.data.decode('utf-8'))
1182
+ except json.JSONDecodeError:
1183
+ response_data = resp.data.decode('utf-8')
1184
+
1185
+ # Return in the same format as the regular API client
1186
+ return [response_data]
1187
+
1188
+ except Exception as e:
1189
+ # Safely handle exception string conversion
1190
+ try:
1191
+ error_str = str(e)
1192
+ except Exception:
1193
+ error_str = f"Exception of type {type(e).__name__}"
1194
+ print(f"ERROR: Network/request error: {error_str}")
1195
+ return None