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.
- catocli/Utils/clidriver.py +207 -211
- catocli/Utils/cliutils.py +178 -0
- catocli/__init__.py +1 -1
- catocli/parsers/custom/customLib.py +1 -1
- catocli/parsers/custom/export_sites/export_sites.py +121 -133
- catocli/parsers/customParserApiClient.py +1195 -0
- catocli/parsers/custom_private/__init__.py +2 -14
- catocli/parsers/mutation_accountManagement/__init__.py +45 -58
- catocli/parsers/mutation_accountManagement_addAccount/README.md +7 -5
- catocli/parsers/mutation_accountManagement_removeAccount/README.md +4 -5
- catocli/parsers/mutation_accountManagement_updateAccount/README.md +7 -5
- catocli/parsers/mutation_admin/__init__.py +84 -45
- catocli/parsers/mutation_admin_addAdmin/README.md +7 -5
- catocli/parsers/mutation_admin_addServicePrincipalAdmin/README.md +19 -0
- catocli/parsers/mutation_admin_removeAdmin/README.md +7 -5
- catocli/parsers/mutation_admin_removeServicePrincipalAdmin/README.md +19 -0
- catocli/parsers/mutation_admin_updateAdmin/README.md +8 -6
- catocli/parsers/mutation_admin_updateServicePrincipalAdmin/README.md +20 -0
- catocli/parsers/mutation_container/__init__.py +135 -135
- catocli/parsers/mutation_container_delete/README.md +7 -5
- catocli/parsers/mutation_container_fqdn_addValues/README.md +7 -5
- catocli/parsers/mutation_container_fqdn_createFromFile/README.md +7 -5
- catocli/parsers/mutation_container_fqdn_removeValues/README.md +7 -5
- catocli/parsers/mutation_container_fqdn_updateFromFile/README.md +7 -5
- catocli/parsers/mutation_container_ipAddressRange_addValues/README.md +7 -5
- catocli/parsers/mutation_container_ipAddressRange_createFromFile/README.md +7 -5
- catocli/parsers/mutation_container_ipAddressRange_removeValues/README.md +7 -5
- catocli/parsers/mutation_container_ipAddressRange_updateFromFile/README.md +7 -5
- catocli/parsers/mutation_enterpriseDirectory/README.md +7 -0
- catocli/parsers/mutation_enterpriseDirectory/__init__.py +61 -0
- catocli/parsers/mutation_enterpriseDirectory_archiveLocation/README.md +19 -0
- catocli/parsers/mutation_enterpriseDirectory_createLocation/README.md +19 -0
- catocli/parsers/mutation_enterpriseDirectory_restoreLocation/README.md +19 -0
- catocli/parsers/mutation_enterpriseDirectory_updateLocation/README.md +19 -0
- catocli/parsers/mutation_groups/__init__.py +45 -45
- catocli/parsers/mutation_groups_createGroup/README.md +8 -6
- catocli/parsers/mutation_groups_deleteGroup/README.md +8 -6
- catocli/parsers/mutation_groups_updateGroup/README.md +8 -6
- catocli/parsers/mutation_hardware/__init__.py +16 -16
- catocli/parsers/mutation_hardware_updateHardwareShipping/README.md +7 -5
- catocli/parsers/mutation_policy/__init__.py +1302 -1302
- catocli/parsers/mutation_policy_appTenantRestriction_addRule/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_addSection/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_createPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_discardPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_moveRule/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_moveSection/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_publishPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_removeRule/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_removeSection/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_updatePolicy/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_updateRule/README.md +8 -6
- catocli/parsers/mutation_policy_appTenantRestriction_updateSection/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_addRule/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_addSection/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_createPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_discardPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_moveRule/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_moveSection/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_publishPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_removeRule/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_removeSection/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_updatePolicy/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_updateRule/README.md +8 -6
- catocli/parsers/mutation_policy_dynamicIpAllocation_updateSection/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_addRule/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_addSection/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_createPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_discardPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_moveRule/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_moveSection/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_publishPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_removeRule/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_removeSection/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_updatePolicy/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_updateRule/README.md +8 -6
- catocli/parsers/mutation_policy_internetFirewall_updateSection/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_addRule/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_addSection/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_createPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_discardPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_moveRule/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_moveSection/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_publishPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_removeRule/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_removeSection/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_updatePolicy/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_updateRule/README.md +8 -6
- catocli/parsers/mutation_policy_remotePortFwd_updateSection/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_addRule/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_addSection/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_createPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_discardPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_moveRule/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_moveSection/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_publishPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_removeRule/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_removeSection/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_updatePolicy/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_updateRule/README.md +8 -6
- catocli/parsers/mutation_policy_socketLan_updateSection/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_addRule/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_addSection/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_createPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_discardPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_moveRule/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_moveSection/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_publishPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_removeRule/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_removeSection/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_updatePolicy/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_updateRule/README.md +8 -6
- catocli/parsers/mutation_policy_terminalServer_updateSection/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_addRule/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_addSection/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_createPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_discardPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_moveRule/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_moveSection/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_publishPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_removeRule/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_removeSection/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_updatePolicy/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_updateRule/README.md +8 -6
- catocli/parsers/mutation_policy_wanFirewall_updateSection/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_addRule/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_addSection/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_createPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_discardPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_moveRule/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_moveSection/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_publishPolicyRevision/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_removeRule/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_removeSection/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_updatePolicy/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_updateRule/README.md +8 -6
- catocli/parsers/mutation_policy_wanNetwork_updateSection/README.md +8 -6
- catocli/parsers/mutation_sandbox/__init__.py +27 -27
- catocli/parsers/mutation_sandbox_deleteReport/README.md +7 -5
- catocli/parsers/mutation_sandbox_uploadFile/README.md +7 -5
- catocli/parsers/mutation_site/__init__.py +474 -474
- catocli/parsers/mutation_site_addBgpPeer/README.md +7 -5
- catocli/parsers/mutation_site_addCloudInterconnectPhysicalConnection/README.md +7 -5
- catocli/parsers/mutation_site_addCloudInterconnectSite/README.md +7 -5
- catocli/parsers/mutation_site_addIpsecIkeV2Site/README.md +7 -5
- catocli/parsers/mutation_site_addIpsecIkeV2SiteTunnels/README.md +8 -6
- catocli/parsers/mutation_site_addNetworkRange/README.md +8 -6
- catocli/parsers/mutation_site_addSecondaryAwsVSocket/README.md +7 -5
- catocli/parsers/mutation_site_addSecondaryAzureVSocket/README.md +7 -5
- catocli/parsers/mutation_site_addSocketAddOnCard/README.md +7 -5
- catocli/parsers/mutation_site_addSocketSite/README.md +7 -5
- catocli/parsers/mutation_site_addStaticHost/README.md +8 -6
- catocli/parsers/mutation_site_assignSiteBwLicense/README.md +7 -5
- catocli/parsers/mutation_site_removeBgpPeer/README.md +7 -5
- catocli/parsers/mutation_site_removeCloudInterconnectPhysicalConnection/README.md +7 -5
- catocli/parsers/mutation_site_removeIpsecIkeV2SiteTunnels/README.md +8 -6
- catocli/parsers/mutation_site_removeNetworkRange/README.md +7 -5
- catocli/parsers/mutation_site_removeSecondaryAwsVSocket/README.md +7 -5
- catocli/parsers/mutation_site_removeSecondaryAzureVSocket/README.md +7 -5
- catocli/parsers/mutation_site_removeSite/README.md +7 -5
- catocli/parsers/mutation_site_removeSiteBwLicense/README.md +7 -5
- catocli/parsers/mutation_site_removeSocketAddOnCard/README.md +7 -5
- catocli/parsers/mutation_site_removeStaticHost/README.md +7 -5
- catocli/parsers/mutation_site_replaceSiteBwLicense/README.md +7 -5
- catocli/parsers/mutation_site_startSiteUpgrade/README.md +7 -5
- catocli/parsers/mutation_site_updateBgpPeer/README.md +7 -5
- catocli/parsers/mutation_site_updateCloudInterconnectPhysicalConnection/README.md +7 -5
- catocli/parsers/mutation_site_updateHa/README.md +8 -6
- catocli/parsers/mutation_site_updateIpsecIkeV2SiteGeneralDetails/README.md +8 -6
- catocli/parsers/mutation_site_updateIpsecIkeV2SiteTunnels/README.md +8 -6
- catocli/parsers/mutation_site_updateNetworkRange/README.md +8 -6
- catocli/parsers/mutation_site_updateSecondaryAwsVSocket/README.md +7 -5
- catocli/parsers/mutation_site_updateSecondaryAzureVSocket/README.md +7 -5
- catocli/parsers/mutation_site_updateSiteBwLicense/README.md +7 -5
- catocli/parsers/mutation_site_updateSiteGeneralDetails/README.md +8 -6
- catocli/parsers/mutation_site_updateSocketInterface/README.md +9 -7
- catocli/parsers/mutation_site_updateStaticHost/README.md +8 -6
- catocli/parsers/mutation_sites/__init__.py +474 -474
- catocli/parsers/mutation_sites_addBgpPeer/README.md +7 -5
- catocli/parsers/mutation_sites_addCloudInterconnectPhysicalConnection/README.md +7 -5
- catocli/parsers/mutation_sites_addCloudInterconnectSite/README.md +7 -5
- catocli/parsers/mutation_sites_addIpsecIkeV2Site/README.md +7 -5
- catocli/parsers/mutation_sites_addIpsecIkeV2SiteTunnels/README.md +8 -6
- catocli/parsers/mutation_sites_addNetworkRange/README.md +8 -6
- catocli/parsers/mutation_sites_addSecondaryAwsVSocket/README.md +7 -5
- catocli/parsers/mutation_sites_addSecondaryAzureVSocket/README.md +7 -5
- catocli/parsers/mutation_sites_addSocketAddOnCard/README.md +7 -5
- catocli/parsers/mutation_sites_addSocketSite/README.md +7 -5
- catocli/parsers/mutation_sites_addStaticHost/README.md +8 -6
- catocli/parsers/mutation_sites_assignSiteBwLicense/README.md +7 -5
- catocli/parsers/mutation_sites_removeBgpPeer/README.md +7 -5
- catocli/parsers/mutation_sites_removeCloudInterconnectPhysicalConnection/README.md +7 -5
- catocli/parsers/mutation_sites_removeIpsecIkeV2SiteTunnels/README.md +8 -6
- catocli/parsers/mutation_sites_removeNetworkRange/README.md +7 -5
- catocli/parsers/mutation_sites_removeSecondaryAwsVSocket/README.md +7 -5
- catocli/parsers/mutation_sites_removeSecondaryAzureVSocket/README.md +7 -5
- catocli/parsers/mutation_sites_removeSite/README.md +7 -5
- catocli/parsers/mutation_sites_removeSiteBwLicense/README.md +7 -5
- catocli/parsers/mutation_sites_removeSocketAddOnCard/README.md +7 -5
- catocli/parsers/mutation_sites_removeStaticHost/README.md +7 -5
- catocli/parsers/mutation_sites_replaceSiteBwLicense/README.md +7 -5
- catocli/parsers/mutation_sites_startSiteUpgrade/README.md +7 -5
- catocli/parsers/mutation_sites_updateBgpPeer/README.md +7 -5
- catocli/parsers/mutation_sites_updateCloudInterconnectPhysicalConnection/README.md +7 -5
- catocli/parsers/mutation_sites_updateHa/README.md +8 -6
- catocli/parsers/mutation_sites_updateIpsecIkeV2SiteGeneralDetails/README.md +8 -6
- catocli/parsers/mutation_sites_updateIpsecIkeV2SiteTunnels/README.md +8 -6
- catocli/parsers/mutation_sites_updateNetworkRange/README.md +8 -6
- catocli/parsers/mutation_sites_updateSecondaryAwsVSocket/README.md +7 -5
- catocli/parsers/mutation_sites_updateSecondaryAzureVSocket/README.md +7 -5
- catocli/parsers/mutation_sites_updateSiteBwLicense/README.md +7 -5
- catocli/parsers/mutation_sites_updateSiteGeneralDetails/README.md +8 -6
- catocli/parsers/mutation_sites_updateSocketInterface/README.md +9 -7
- catocli/parsers/mutation_sites_updateStaticHost/README.md +8 -6
- catocli/parsers/mutation_xdr/__init__.py +45 -45
- catocli/parsers/mutation_xdr_addStoryComment/README.md +7 -5
- catocli/parsers/mutation_xdr_analystFeedback/README.md +7 -6
- catocli/parsers/mutation_xdr_deleteStoryComment/README.md +7 -5
- catocli/parsers/query_accountBySubdomain/README.md +7 -5
- catocli/parsers/query_accountBySubdomain/__init__.py +12 -12
- catocli/parsers/query_accountManagement/README.md +4 -5
- catocli/parsers/query_accountManagement/__init__.py +12 -12
- catocli/parsers/query_accountMetrics/README.md +20 -18
- catocli/parsers/query_accountMetrics/__init__.py +12 -12
- catocli/parsers/query_accountRoles/README.md +7 -5
- catocli/parsers/query_accountRoles/__init__.py +12 -12
- catocli/parsers/query_accountSnapshot/README.md +8 -6
- catocli/parsers/query_accountSnapshot/__init__.py +12 -12
- catocli/parsers/query_admin/README.md +7 -5
- catocli/parsers/query_admin/__init__.py +12 -12
- catocli/parsers/query_admins/README.md +11 -9
- catocli/parsers/query_admins/__init__.py +12 -12
- catocli/parsers/query_appStats/README.md +20 -11
- catocli/parsers/query_appStats/__init__.py +12 -12
- catocli/parsers/query_appStatsTimeSeries/README.md +21 -12
- catocli/parsers/query_appStatsTimeSeries/__init__.py +12 -12
- catocli/parsers/query_auditFeed/README.md +10 -8
- catocli/parsers/query_auditFeed/__init__.py +12 -12
- catocli/parsers/query_catalogs/README.md +9 -7
- catocli/parsers/query_catalogs/__init__.py +12 -12
- catocli/parsers/query_container/README.md +13 -11
- catocli/parsers/query_container/__init__.py +12 -12
- catocli/parsers/query_devices/README.md +10 -8
- catocli/parsers/query_devices/__init__.py +12 -12
- catocli/parsers/query_enterpriseDirectory/README.md +19 -0
- catocli/parsers/query_enterpriseDirectory/__init__.py +16 -0
- catocli/parsers/query_entityLookup/README.md +19 -17
- catocli/parsers/query_entityLookup/__init__.py +12 -12
- catocli/parsers/query_events/README.md +13 -11
- catocli/parsers/query_events/__init__.py +12 -12
- catocli/parsers/query_eventsFeed/README.md +9 -7
- catocli/parsers/query_eventsFeed/__init__.py +12 -12
- catocli/parsers/query_eventsTimeSeries/README.md +14 -12
- catocli/parsers/query_eventsTimeSeries/__init__.py +12 -12
- catocli/parsers/query_groups/__init__.py +51 -51
- catocli/parsers/query_groups_groupList/README.md +8 -6
- catocli/parsers/query_groups_group_members/README.md +8 -6
- catocli/parsers/query_groups_whereUsed/README.md +7 -5
- catocli/parsers/query_hardware/README.md +7 -5
- catocli/parsers/query_hardware/__init__.py +12 -12
- catocli/parsers/query_hardwareManagement/README.md +7 -5
- catocli/parsers/query_hardwareManagement/__init__.py +12 -12
- catocli/parsers/query_licensing/README.md +4 -5
- catocli/parsers/query_licensing/__init__.py +12 -12
- catocli/parsers/query_policy/__init__.py +158 -158
- catocli/parsers/query_policy_appTenantRestriction_policy/README.md +7 -5
- catocli/parsers/query_policy_dynamicIpAllocation_policy/README.md +7 -5
- catocli/parsers/query_policy_internetFirewall_policy/README.md +7 -5
- catocli/parsers/query_policy_remotePortFwd_policy/README.md +7 -5
- catocli/parsers/query_policy_socketLan_policy/README.md +7 -5
- catocli/parsers/query_policy_terminalServer_policy/README.md +7 -5
- catocli/parsers/query_policy_wanFirewall_policy/README.md +7 -5
- catocli/parsers/query_policy_wanNetwork_policy/README.md +7 -5
- catocli/parsers/query_popLocations/README.md +7 -5
- catocli/parsers/query_popLocations/__init__.py +12 -12
- catocli/parsers/query_sandbox/README.md +7 -5
- catocli/parsers/query_sandbox/__init__.py +12 -12
- catocli/parsers/query_servicePrincipalAdmin/README.md +19 -0
- catocli/parsers/query_servicePrincipalAdmin/__init__.py +16 -0
- catocli/parsers/query_site/__init__.py +123 -123
- catocli/parsers/query_siteLocation/__init__.py +10 -10
- catocli/parsers/query_site_availableVersionList/README.md +7 -5
- catocli/parsers/query_site_bgpPeer/README.md +7 -5
- catocli/parsers/query_site_bgpPeerList/README.md +7 -5
- catocli/parsers/query_site_cloudInterconnectConnectionConnectivity/README.md +7 -5
- catocli/parsers/query_site_cloudInterconnectPhysicalConnection/README.md +7 -5
- catocli/parsers/query_site_cloudInterconnectPhysicalConnectionId/README.md +7 -5
- catocli/parsers/query_site_secondaryAwsVSocket/README.md +7 -5
- catocli/parsers/query_site_secondaryAzureVSocket/README.md +7 -5
- catocli/parsers/query_site_siteBgpStatus/README.md +7 -5
- catocli/parsers/query_socketPortMetrics/README.md +13 -11
- catocli/parsers/query_socketPortMetrics/__init__.py +12 -12
- catocli/parsers/query_socketPortMetricsTimeSeries/README.md +14 -12
- catocli/parsers/query_socketPortMetricsTimeSeries/__init__.py +12 -12
- catocli/parsers/query_subDomains/README.md +7 -5
- catocli/parsers/query_subDomains/__init__.py +12 -12
- catocli/parsers/query_xdr/__init__.py +27 -27
- catocli/parsers/query_xdr_stories/README.md +7 -6
- catocli/parsers/query_xdr_story/README.md +9 -8
- catocli/parsers/raw/__init__.py +9 -9
- {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/METADATA +1 -1
- catocli-2.1.3.dist-info/RECORD +660 -0
- models/mutation.accountManagement.addAccount.json +5 -15
- models/mutation.accountManagement.updateAccount.json +1 -3
- models/mutation.admin.addAdmin.json +14 -35
- models/mutation.admin.addServicePrincipalAdmin.json +2554 -0
- models/mutation.admin.removeAdmin.json +1 -1
- models/mutation.admin.removeServicePrincipalAdmin.json +201 -0
- models/mutation.admin.updateAdmin.json +13 -30
- models/mutation.admin.updateServicePrincipalAdmin.json +2554 -0
- models/mutation.container.delete.json +9 -435
- models/mutation.container.fqdn.addValues.json +6 -11
- models/mutation.container.fqdn.createFromFile.json +4 -12
- models/mutation.container.fqdn.removeValues.json +6 -11
- models/mutation.container.fqdn.updateFromFile.json +5 -15
- models/mutation.container.ipAddressRange.addValues.json +4 -12
- models/mutation.container.ipAddressRange.createFromFile.json +4 -12
- models/mutation.container.ipAddressRange.removeValues.json +4 -12
- models/mutation.container.ipAddressRange.updateFromFile.json +5 -15
- models/mutation.enterpriseDirectory.archiveLocation.json +1046 -0
- models/mutation.enterpriseDirectory.createLocation.json +2521 -0
- models/mutation.enterpriseDirectory.restoreLocation.json +1046 -0
- models/mutation.enterpriseDirectory.updateLocation.json +2567 -0
- models/mutation.groups.createGroup.json +39 -171
- models/mutation.groups.deleteGroup.json +36 -162
- models/mutation.groups.updateGroup.json +41 -177
- models/mutation.hardware.updateHardwareShipping.json +18 -47
- models/mutation.policy.appTenantRestriction.addRule.json +50 -136
- models/mutation.policy.appTenantRestriction.addSection.json +4 -12
- models/mutation.policy.appTenantRestriction.createPolicyRevision.json +3 -9
- models/mutation.policy.appTenantRestriction.discardPolicyRevision.json +2 -6
- models/mutation.policy.appTenantRestriction.moveRule.json +4 -12
- models/mutation.policy.appTenantRestriction.moveSection.json +4 -12
- models/mutation.policy.appTenantRestriction.publishPolicyRevision.json +3 -9
- models/mutation.policy.appTenantRestriction.removeRule.json +2 -6
- models/mutation.policy.appTenantRestriction.removeSection.json +2 -6
- models/mutation.policy.appTenantRestriction.updatePolicy.json +2 -6
- models/mutation.policy.appTenantRestriction.updateRule.json +49 -133
- models/mutation.policy.appTenantRestriction.updateSection.json +3 -9
- models/mutation.policy.dynamicIpAllocation.addRule.json +15 -45
- models/mutation.policy.dynamicIpAllocation.addSection.json +4 -12
- models/mutation.policy.dynamicIpAllocation.createPolicyRevision.json +3 -9
- models/mutation.policy.dynamicIpAllocation.discardPolicyRevision.json +2 -6
- models/mutation.policy.dynamicIpAllocation.moveRule.json +4 -12
- models/mutation.policy.dynamicIpAllocation.moveSection.json +4 -12
- models/mutation.policy.dynamicIpAllocation.publishPolicyRevision.json +3 -9
- models/mutation.policy.dynamicIpAllocation.removeRule.json +2 -6
- models/mutation.policy.dynamicIpAllocation.removeSection.json +2 -6
- models/mutation.policy.dynamicIpAllocation.updatePolicy.json +2 -6
- models/mutation.policy.dynamicIpAllocation.updateRule.json +14 -42
- models/mutation.policy.dynamicIpAllocation.updateSection.json +3 -9
- models/mutation.policy.internetFirewall.addRule.json +248 -542
- models/mutation.policy.internetFirewall.addSection.json +4 -12
- models/mutation.policy.internetFirewall.createPolicyRevision.json +3 -9
- models/mutation.policy.internetFirewall.discardPolicyRevision.json +2 -6
- models/mutation.policy.internetFirewall.moveRule.json +4 -12
- models/mutation.policy.internetFirewall.moveSection.json +4 -12
- models/mutation.policy.internetFirewall.publishPolicyRevision.json +3 -9
- models/mutation.policy.internetFirewall.removeRule.json +2 -6
- models/mutation.policy.internetFirewall.removeSection.json +2 -6
- models/mutation.policy.internetFirewall.updatePolicy.json +2 -6
- models/mutation.policy.internetFirewall.updateRule.json +247 -539
- models/mutation.policy.internetFirewall.updateSection.json +3 -9
- models/mutation.policy.remotePortFwd.addRule.json +35 -91
- models/mutation.policy.remotePortFwd.addSection.json +4 -12
- models/mutation.policy.remotePortFwd.createPolicyRevision.json +3 -9
- models/mutation.policy.remotePortFwd.discardPolicyRevision.json +2 -6
- models/mutation.policy.remotePortFwd.moveRule.json +4 -12
- models/mutation.policy.remotePortFwd.moveSection.json +4 -12
- models/mutation.policy.remotePortFwd.publishPolicyRevision.json +3 -9
- models/mutation.policy.remotePortFwd.removeRule.json +2 -6
- models/mutation.policy.remotePortFwd.removeSection.json +2 -6
- models/mutation.policy.remotePortFwd.updatePolicy.json +2 -6
- models/mutation.policy.remotePortFwd.updateRule.json +34 -88
- models/mutation.policy.remotePortFwd.updateSection.json +3 -9
- models/mutation.policy.socketLan.addRule.json +78 -185
- models/mutation.policy.socketLan.addSection.json +4 -12
- models/mutation.policy.socketLan.createPolicyRevision.json +3 -9
- models/mutation.policy.socketLan.discardPolicyRevision.json +2 -6
- models/mutation.policy.socketLan.moveRule.json +4 -12
- models/mutation.policy.socketLan.moveSection.json +4 -12
- models/mutation.policy.socketLan.publishPolicyRevision.json +3 -9
- models/mutation.policy.socketLan.removeRule.json +2 -6
- models/mutation.policy.socketLan.removeSection.json +2 -6
- models/mutation.policy.socketLan.updatePolicy.json +2 -6
- models/mutation.policy.socketLan.updateRule.json +77 -182
- models/mutation.policy.socketLan.updateSection.json +3 -9
- models/mutation.policy.terminalServer.addRule.json +10 -30
- models/mutation.policy.terminalServer.addSection.json +4 -12
- models/mutation.policy.terminalServer.createPolicyRevision.json +3 -9
- models/mutation.policy.terminalServer.discardPolicyRevision.json +2 -6
- models/mutation.policy.terminalServer.moveRule.json +4 -12
- models/mutation.policy.terminalServer.moveSection.json +4 -12
- models/mutation.policy.terminalServer.publishPolicyRevision.json +3 -9
- models/mutation.policy.terminalServer.removeRule.json +2 -6
- models/mutation.policy.terminalServer.removeSection.json +2 -6
- models/mutation.policy.terminalServer.updatePolicy.json +2 -6
- models/mutation.policy.terminalServer.updateRule.json +9 -27
- models/mutation.policy.terminalServer.updateSection.json +3 -9
- models/mutation.policy.wanFirewall.addRule.json +290 -654
- models/mutation.policy.wanFirewall.addSection.json +4 -12
- models/mutation.policy.wanFirewall.createPolicyRevision.json +3 -9
- models/mutation.policy.wanFirewall.discardPolicyRevision.json +2 -6
- models/mutation.policy.wanFirewall.moveRule.json +4 -12
- models/mutation.policy.wanFirewall.moveSection.json +4 -12
- models/mutation.policy.wanFirewall.publishPolicyRevision.json +3 -9
- models/mutation.policy.wanFirewall.removeRule.json +2 -6
- models/mutation.policy.wanFirewall.removeSection.json +2 -6
- models/mutation.policy.wanFirewall.updatePolicy.json +2 -6
- models/mutation.policy.wanFirewall.updateRule.json +289 -651
- models/mutation.policy.wanFirewall.updateSection.json +3 -9
- models/mutation.policy.wanNetwork.addRule.json +204 -514
- models/mutation.policy.wanNetwork.addSection.json +4 -12
- models/mutation.policy.wanNetwork.createPolicyRevision.json +3 -9
- models/mutation.policy.wanNetwork.discardPolicyRevision.json +2 -6
- models/mutation.policy.wanNetwork.moveRule.json +4 -12
- models/mutation.policy.wanNetwork.moveSection.json +4 -12
- models/mutation.policy.wanNetwork.publishPolicyRevision.json +3 -9
- models/mutation.policy.wanNetwork.removeRule.json +2 -6
- models/mutation.policy.wanNetwork.removeSection.json +2 -6
- models/mutation.policy.wanNetwork.updatePolicy.json +2 -6
- models/mutation.policy.wanNetwork.updateRule.json +203 -511
- models/mutation.policy.wanNetwork.updateSection.json +3 -9
- models/mutation.sandbox.deleteReport.json +1 -3
- models/mutation.sandbox.uploadFile.json +1 -3
- models/mutation.site.addBgpPeer.json +48 -123
- models/mutation.site.addCloudInterconnectPhysicalConnection.json +12 -36
- models/mutation.site.addCloudInterconnectSite.json +8 -24
- models/mutation.site.addIpsecIkeV2Site.json +10 -30
- models/mutation.site.addIpsecIkeV2SiteTunnels.json +14 -40
- models/mutation.site.addNetworkRange.json +15 -43
- models/mutation.site.addSecondaryAwsVSocket.json +5 -15
- models/mutation.site.addSecondaryAzureVSocket.json +4 -12
- models/mutation.site.addSocketAddOnCard.json +4 -12
- models/mutation.site.addSocketSite.json +12 -36
- models/mutation.site.addStaticHost.json +4 -10
- models/mutation.site.assignSiteBwLicense.json +120 -11782
- models/mutation.site.removeBgpPeer.json +1 -3
- models/mutation.site.removeCloudInterconnectPhysicalConnection.json +1 -3
- models/mutation.site.removeIpsecIkeV2SiteTunnels.json +2 -4
- models/mutation.site.removeNetworkRange.json +1 -1
- models/mutation.site.removeSecondaryAwsVSocket.json +1 -1
- models/mutation.site.removeSecondaryAzureVSocket.json +1 -1
- models/mutation.site.removeSite.json +1 -1
- models/mutation.site.removeSiteBwLicense.json +119 -11779
- models/mutation.site.removeSocketAddOnCard.json +3 -9
- models/mutation.site.removeStaticHost.json +1 -1
- models/mutation.site.replaceSiteBwLicense.json +121 -11785
- models/mutation.site.startSiteUpgrade.json +3 -9
- models/mutation.site.updateBgpPeer.json +47 -120
- models/mutation.site.updateCloudInterconnectPhysicalConnection.json +10 -30
- models/mutation.site.updateHa.json +4 -10
- models/mutation.site.updateIpsecIkeV2SiteGeneralDetails.json +11 -24
- models/mutation.site.updateIpsecIkeV2SiteTunnels.json +15 -43
- models/mutation.site.updateNetworkRange.json +15 -43
- models/mutation.site.updateSecondaryAwsVSocket.json +4 -12
- models/mutation.site.updateSecondaryAzureVSocket.json +3 -9
- models/mutation.site.updateSiteBwLicense.json +120 -11782
- models/mutation.site.updateSiteGeneralDetails.json +14 -40
- models/mutation.site.updateSocketInterface.json +26 -74
- models/mutation.site.updateStaticHost.json +4 -10
- models/mutation.sites.addBgpPeer.json +48 -123
- models/mutation.sites.addCloudInterconnectPhysicalConnection.json +12 -36
- models/mutation.sites.addCloudInterconnectSite.json +8 -24
- models/mutation.sites.addIpsecIkeV2Site.json +10 -30
- models/mutation.sites.addIpsecIkeV2SiteTunnels.json +14 -40
- models/mutation.sites.addNetworkRange.json +15 -43
- models/mutation.sites.addSecondaryAwsVSocket.json +5 -15
- models/mutation.sites.addSecondaryAzureVSocket.json +4 -12
- models/mutation.sites.addSocketAddOnCard.json +4 -12
- models/mutation.sites.addSocketSite.json +12 -36
- models/mutation.sites.addStaticHost.json +4 -10
- models/mutation.sites.assignSiteBwLicense.json +120 -11782
- models/mutation.sites.removeBgpPeer.json +1 -3
- models/mutation.sites.removeCloudInterconnectPhysicalConnection.json +1 -3
- models/mutation.sites.removeIpsecIkeV2SiteTunnels.json +2 -4
- models/mutation.sites.removeNetworkRange.json +1 -1
- models/mutation.sites.removeSecondaryAwsVSocket.json +1 -1
- models/mutation.sites.removeSecondaryAzureVSocket.json +1 -1
- models/mutation.sites.removeSite.json +1 -1
- models/mutation.sites.removeSiteBwLicense.json +119 -11779
- models/mutation.sites.removeSocketAddOnCard.json +3 -9
- models/mutation.sites.removeStaticHost.json +1 -1
- models/mutation.sites.replaceSiteBwLicense.json +121 -11785
- models/mutation.sites.startSiteUpgrade.json +3 -9
- models/mutation.sites.updateBgpPeer.json +47 -120
- models/mutation.sites.updateCloudInterconnectPhysicalConnection.json +10 -30
- models/mutation.sites.updateHa.json +4 -10
- models/mutation.sites.updateIpsecIkeV2SiteGeneralDetails.json +11 -24
- models/mutation.sites.updateIpsecIkeV2SiteTunnels.json +15 -43
- models/mutation.sites.updateNetworkRange.json +15 -43
- models/mutation.sites.updateSecondaryAwsVSocket.json +4 -12
- models/mutation.sites.updateSecondaryAzureVSocket.json +3 -9
- models/mutation.sites.updateSiteBwLicense.json +120 -11782
- models/mutation.sites.updateSiteGeneralDetails.json +14 -40
- models/mutation.sites.updateSocketInterface.json +26 -74
- models/mutation.sites.updateStaticHost.json +4 -10
- models/mutation.xdr.addStoryComment.json +15 -113
- models/mutation.xdr.analystFeedback.json +229 -26307
- models/mutation.xdr.deleteStoryComment.json +15 -113
- models/query.accountBySubdomain.json +2 -1
- models/query.accountMetrics.json +15 -12
- models/query.accountRoles.json +1 -1
- models/query.accountSnapshot.json +4 -2
- models/query.admin.json +1 -1
- models/query.admins.json +7 -10
- models/query.appStats.json +633 -33
- models/query.appStatsTimeSeries.json +15 -28
- models/query.auditFeed.json +225 -16
- models/query.catalogs.json +106 -215
- models/query.container.json +21 -471
- models/query.devices.json +333 -620
- models/query.enterpriseDirectory.json +3596 -0
- models/query.entityLookup.json +15 -27
- models/query.events.json +1875 -33
- models/query.eventsFeed.json +30 -14
- models/query.eventsTimeSeries.json +15 -28
- models/query.groups.group.members.json +23 -55
- models/query.groups.groupList.json +87 -266
- models/query.groups.whereUsed.json +2 -6
- models/query.hardware.json +92 -192
- models/query.hardwareManagement.json +21 -63
- models/query.licensing.json +76 -11721
- models/query.policy.appTenantRestriction.policy.json +2 -6
- models/query.policy.dynamicIpAllocation.policy.json +2 -6
- models/query.policy.internetFirewall.policy.json +2 -6
- models/query.policy.remotePortFwd.policy.json +2 -6
- models/query.policy.socketLan.policy.json +2 -6
- models/query.policy.terminalServer.policy.json +2 -6
- models/query.policy.wanFirewall.policy.json +2 -6
- models/query.policy.wanNetwork.policy.json +2 -6
- models/query.popLocations.json +34 -74
- models/query.sandbox.json +44 -83
- models/query.servicePrincipalAdmin.json +1098 -0
- models/query.site.availableVersionList.json +4 -5
- models/query.site.bgpPeer.json +2 -6
- models/query.site.bgpPeerList.json +2 -6
- models/query.site.cloudInterconnectConnectionConnectivity.json +1 -3
- models/query.site.cloudInterconnectPhysicalConnection.json +1 -3
- models/query.site.cloudInterconnectPhysicalConnectionId.json +3 -9
- models/query.site.secondaryAwsVSocket.json +1 -1
- models/query.site.secondaryAzureVSocket.json +1 -1
- models/query.site.siteBgpStatus.json +2 -6
- models/query.socketPortMetrics.json +465 -33
- models/query.socketPortMetricsTimeSeries.json +15 -28
- models/query.subDomains.json +1 -1
- models/query.xdr.stories.json +306 -26440
- models/query.xdr.story.json +40 -26100
- schema/catolib.py +1255 -972
- schema/importSchema.py +53 -48
- catocli/parsers/mutation/README.md +0 -4
- catocli/parsers/mutation_accountManagement_disableAccount/README.md +0 -16
- catocli/parsers/parserApiClient.py +0 -948
- catocli/parsers/query/README.md +0 -3
- catocli-2.1.1.dist-info/RECORD +0 -645
- models/mutation.accountManagement.disableAccount.json +0 -545
- models/query.policy.json +0 -40899
- models/query.site.json +0 -5688
- schema/remove_policyid.py +0 -89
- schema/remove_policyid_mutations.py +0 -89
- {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/WHEEL +0 -0
- {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/entry_points.txt +0 -0
- {catocli-2.1.1.dist-info → catocli-2.1.3.dist-info}/licenses/LICENSE +0 -0
- {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
|