wxcli 1.2.0__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.
- wxcli/__init__.py +4 -0
- wxcli/_playbook/.claude/agents/migration-advisor.md +239 -0
- wxcli/_playbook/.claude/agents/wxc-calling-builder.md +946 -0
- wxcli/_playbook/.claude/rules/cleanup.md +29 -0
- wxcli/_playbook/.claude/rules/cucm-migration.md +58 -0
- wxcli/_playbook/.claude/rules/org-health.md +14 -0
- wxcli/_playbook/.claude/settings.json +8 -0
- wxcli/_playbook/.claude/skills/audit-compliance/SKILL.md +515 -0
- wxcli/_playbook/.claude/skills/call-control/SKILL.md +747 -0
- wxcli/_playbook/.claude/skills/configure-features/SKILL.md +623 -0
- wxcli/_playbook/.claude/skills/configure-routing/SKILL.md +627 -0
- wxcli/_playbook/.claude/skills/contact-center/SKILL.md +1181 -0
- wxcli/_playbook/.claude/skills/cucm-migrate/SKILL.md +820 -0
- wxcli/_playbook/.claude/skills/customer-assist/SKILL.md +443 -0
- wxcli/_playbook/.claude/skills/device-platform/SKILL.md +407 -0
- wxcli/_playbook/.claude/skills/manage-call-settings/SKILL.md +757 -0
- wxcli/_playbook/.claude/skills/manage-devices/SKILL.md +790 -0
- wxcli/_playbook/.claude/skills/manage-identity/SKILL.md +638 -0
- wxcli/_playbook/.claude/skills/manage-licensing/SKILL.md +528 -0
- wxcli/_playbook/.claude/skills/manage-meetings/SKILL.md +708 -0
- wxcli/_playbook/.claude/skills/messaging-bots/SKILL.md +365 -0
- wxcli/_playbook/.claude/skills/messaging-spaces/SKILL.md +520 -0
- wxcli/_playbook/.claude/skills/org-health/SKILL.md +159 -0
- wxcli/_playbook/.claude/skills/provision-calling/SKILL.md +421 -0
- wxcli/_playbook/.claude/skills/query-live/SKILL.md +183 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-flow-trace.md +223 -0
- wxcli/_playbook/.claude/skills/query-live/domains/call-history.md +44 -0
- wxcli/_playbook/.claude/skills/query-live/domains/features.md +169 -0
- wxcli/_playbook/.claude/skills/query-live/domains/numbers.md +93 -0
- wxcli/_playbook/.claude/skills/query-live/domains/people-and-settings.md +146 -0
- wxcli/_playbook/.claude/skills/query-live/domains/routing.md +124 -0
- wxcli/_playbook/.claude/skills/reporting/SKILL.md +501 -0
- wxcli/_playbook/.claude/skills/reporting/references/cdr-recipes.md +1628 -0
- wxcli/_playbook/.claude/skills/reporting-cc/SKILL.md +251 -0
- wxcli/_playbook/.claude/skills/reporting-meetings/SKILL.md +290 -0
- wxcli/_playbook/.claude/skills/teardown/SKILL.md +214 -0
- wxcli/_playbook/.claude/skills/video-mesh/SKILL.md +379 -0
- wxcli/_playbook/.claude/skills/wxc-calling-debug/SKILL.md +475 -0
- wxcli/_playbook/CLAUDE.md +373 -0
- wxcli/_playbook/docs/reference/CLAUDE.md +25 -0
- wxcli/_playbook/docs/reference/admin-apps-data.md +643 -0
- wxcli/_playbook/docs/reference/admin-audit-security.md +322 -0
- wxcli/_playbook/docs/reference/admin-hybrid.md +383 -0
- wxcli/_playbook/docs/reference/admin-identity-scim.md +1007 -0
- wxcli/_playbook/docs/reference/admin-licensing.md +414 -0
- wxcli/_playbook/docs/reference/admin-org-management.md +586 -0
- wxcli/_playbook/docs/reference/admin-partner.md +357 -0
- wxcli/_playbook/docs/reference/archive/wxc-sdk-patterns.md +1229 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-advanced.md +835 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-core.md +743 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-devices-workspaces.md +1305 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-features.md +849 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-locations.md +938 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-person.md +1255 -0
- wxcli/_playbook/docs/reference/archive/wxcadm-routing.md +1262 -0
- wxcli/_playbook/docs/reference/authentication.md +965 -0
- wxcli/_playbook/docs/reference/call-control.md +1205 -0
- wxcli/_playbook/docs/reference/call-features-additional.md +2541 -0
- wxcli/_playbook/docs/reference/call-features-major.md +1320 -0
- wxcli/_playbook/docs/reference/call-routing.md +2304 -0
- wxcli/_playbook/docs/reference/contact-center-analytics.md +1049 -0
- wxcli/_playbook/docs/reference/contact-center-core.md +1614 -0
- wxcli/_playbook/docs/reference/contact-center-journey.md +457 -0
- wxcli/_playbook/docs/reference/contact-center-routing.md +1138 -0
- wxcli/_playbook/docs/reference/devices-core.md +1869 -0
- wxcli/_playbook/docs/reference/devices-dect.md +1150 -0
- wxcli/_playbook/docs/reference/devices-platform.md +467 -0
- wxcli/_playbook/docs/reference/devices-workspaces.md +1426 -0
- wxcli/_playbook/docs/reference/emergency-services.md +1122 -0
- wxcli/_playbook/docs/reference/location-calling-core.md +2037 -0
- wxcli/_playbook/docs/reference/location-calling-media.md +1334 -0
- wxcli/_playbook/docs/reference/location-recording-advanced.md +1482 -0
- wxcli/_playbook/docs/reference/meetings-content.md +473 -0
- wxcli/_playbook/docs/reference/meetings-core.md +903 -0
- wxcli/_playbook/docs/reference/meetings-infrastructure.md +899 -0
- wxcli/_playbook/docs/reference/meetings-settings.md +850 -0
- wxcli/_playbook/docs/reference/messaging-bots.md +1091 -0
- wxcli/_playbook/docs/reference/messaging-spaces.md +695 -0
- wxcli/_playbook/docs/reference/person-call-settings-behavior.md +1639 -0
- wxcli/_playbook/docs/reference/person-call-settings-handling.md +1394 -0
- wxcli/_playbook/docs/reference/person-call-settings-media.md +1898 -0
- wxcli/_playbook/docs/reference/person-call-settings-permissions.md +1329 -0
- wxcli/_playbook/docs/reference/provisioning.md +1366 -0
- wxcli/_playbook/docs/reference/reporting-analytics.md +1318 -0
- wxcli/_playbook/docs/reference/self-service-call-settings.md +1020 -0
- wxcli/_playbook/docs/reference/virtual-lines.md +1132 -0
- wxcli/_playbook/docs/reference/webhooks-events.md +1444 -0
- wxcli/_playbook/docs/reference/wxcadm-xsi-realtime.md +953 -0
- wxcli/_version.py +24 -0
- wxcli/auth.py +137 -0
- wxcli/commands/__init__.py +0 -0
- wxcli/commands/_registry.py +181 -0
- wxcli/commands/activation_email.py +103 -0
- wxcli/commands/admin_recordings.py +585 -0
- wxcli/commands/analytics.py +105 -0
- wxcli/commands/announcement_playlists.py +219 -0
- wxcli/commands/announcements.py +519 -0
- wxcli/commands/archive_users.py +36 -0
- wxcli/commands/attachment_actions.py +60 -0
- wxcli/commands/audit_events.py +83 -0
- wxcli/commands/authorizations.py +113 -0
- wxcli/commands/auto_attendant.py +642 -0
- wxcli/commands/broadworks_billing_reports.py +126 -0
- wxcli/commands/broadworks_enterprises.py +160 -0
- wxcli/commands/broadworks_subscribers.py +319 -0
- wxcli/commands/broadworks_workspaces.py +108 -0
- wxcli/commands/call_controls.py +1304 -0
- wxcli/commands/call_park.py +494 -0
- wxcli/commands/call_pickup.py +229 -0
- wxcli/commands/call_queue.py +1878 -0
- wxcli/commands/call_recording.py +707 -0
- wxcli/commands/call_routing.py +1610 -0
- wxcli/commands/call_settings_for_me_phase_5.py +212 -0
- wxcli/commands/caller_reputation.py +169 -0
- wxcli/commands/calling_service.py +305 -0
- wxcli/commands/cc_address_book.py +770 -0
- wxcli/commands/cc_agent_greetings.py +242 -0
- wxcli/commands/cc_agent_summaries.py +39 -0
- wxcli/commands/cc_agent_wellbeing.py +216 -0
- wxcli/commands/cc_agents.py +425 -0
- wxcli/commands/cc_ai_assistant.py +55 -0
- wxcli/commands/cc_ai_feature.py +294 -0
- wxcli/commands/cc_audio_files.py +149 -0
- wxcli/commands/cc_auto_csat.py +311 -0
- wxcli/commands/cc_aux_code.py +458 -0
- wxcli/commands/cc_business_hour.py +353 -0
- wxcli/commands/cc_call_monitoring.py +228 -0
- wxcli/commands/cc_callbacks.py +194 -0
- wxcli/commands/cc_campaign.py +212 -0
- wxcli/commands/cc_captures.py +39 -0
- wxcli/commands/cc_contact_list.py +169 -0
- wxcli/commands/cc_contact_number.py +295 -0
- wxcli/commands/cc_data_sources.py +235 -0
- wxcli/commands/cc_desktop_layout.py +380 -0
- wxcli/commands/cc_desktop_profile.py +322 -0
- wxcli/commands/cc_dial_number.py +511 -0
- wxcli/commands/cc_dial_plan.py +347 -0
- wxcli/commands/cc_dnc.py +101 -0
- wxcli/commands/cc_entry_point.py +545 -0
- wxcli/commands/cc_ewt.py +51 -0
- wxcli/commands/cc_flow.py +477 -0
- wxcli/commands/cc_global_vars.py +430 -0
- wxcli/commands/cc_holiday_list.py +335 -0
- wxcli/commands/cc_journey.py +1261 -0
- wxcli/commands/cc_legacy_flows.py +49 -0
- wxcli/commands/cc_multimedia_profile.py +436 -0
- wxcli/commands/cc_notification.py +51 -0
- wxcli/commands/cc_outdial_ani.py +604 -0
- wxcli/commands/cc_overrides.py +341 -0
- wxcli/commands/cc_queue.py +1083 -0
- wxcli/commands/cc_queue_stats.py +55 -0
- wxcli/commands/cc_realtime.py +51 -0
- wxcli/commands/cc_resource_collection.py +312 -0
- wxcli/commands/cc_search.py +46 -0
- wxcli/commands/cc_site.py +400 -0
- wxcli/commands/cc_skill.py +350 -0
- wxcli/commands/cc_skill_profile.py +289 -0
- wxcli/commands/cc_subscriptions.py +426 -0
- wxcli/commands/cc_summaries.py +130 -0
- wxcli/commands/cc_tasks.py +844 -0
- wxcli/commands/cc_team.py +325 -0
- wxcli/commands/cc_user_profiles.py +349 -0
- wxcli/commands/cc_users.py +701 -0
- wxcli/commands/cc_work_types.py +363 -0
- wxcli/commands/cdr.py +89 -0
- wxcli/commands/classifications.py +38 -0
- wxcli/commands/cleanup.py +1427 -0
- wxcli/commands/client_settings.py +72 -0
- wxcli/commands/conference.py +328 -0
- wxcli/commands/configure.py +72 -0
- wxcli/commands/converged_recordings.py +347 -0
- wxcli/commands/converged_recordings_export.py +310 -0
- wxcli/commands/cq_playlists.py +42 -0
- wxcli/commands/cucm.py +2794 -0
- wxcli/commands/cucm_config.py +129 -0
- wxcli/commands/customer_assist.py +373 -0
- wxcli/commands/data_sources.py +227 -0
- wxcli/commands/dect_devices.py +787 -0
- wxcli/commands/device_configurations.py +77 -0
- wxcli/commands/device_dynamic_settings.py +320 -0
- wxcli/commands/device_settings.py +1489 -0
- wxcli/commands/devices.py +280 -0
- wxcli/commands/domains.py +142 -0
- wxcli/commands/ecm.py +178 -0
- wxcli/commands/emergency_services.py +827 -0
- wxcli/commands/events.py +83 -0
- wxcli/commands/external_voicemail.py +52 -0
- wxcli/commands/groups.py +206 -0
- wxcli/commands/guest_management.py +76 -0
- wxcli/commands/hds.py +340 -0
- wxcli/commands/hot_desk.py +67 -0
- wxcli/commands/hot_desking_members.py +118 -0
- wxcli/commands/hot_desking_portal.py +124 -0
- wxcli/commands/hunt_group.py +577 -0
- wxcli/commands/hybrid_clusters.py +71 -0
- wxcli/commands/hybrid_connectors.py +67 -0
- wxcli/commands/identity_org.py +159 -0
- wxcli/commands/init_playbook.py +155 -0
- wxcli/commands/licenses.py +106 -0
- wxcli/commands/live_monitoring.py +40 -0
- wxcli/commands/location_call_handling.py +577 -0
- wxcli/commands/location_schedules.py +341 -0
- wxcli/commands/location_settings.py +1297 -0
- wxcli/commands/location_voicemail.py +494 -0
- wxcli/commands/locations.py +336 -0
- wxcli/commands/meeting_captions.py +110 -0
- wxcli/commands/meeting_chats.py +63 -0
- wxcli/commands/meeting_invitees.py +234 -0
- wxcli/commands/meeting_messages.py +28 -0
- wxcli/commands/meeting_participants.py +278 -0
- wxcli/commands/meeting_polls.py +111 -0
- wxcli/commands/meeting_preferences.py +515 -0
- wxcli/commands/meeting_qa.py +78 -0
- wxcli/commands/meeting_qualities.py +43 -0
- wxcli/commands/meeting_reports.py +101 -0
- wxcli/commands/meeting_session_types.py +105 -0
- wxcli/commands/meeting_site.py +57 -0
- wxcli/commands/meeting_slido.py +44 -0
- wxcli/commands/meeting_summaries.py +92 -0
- wxcli/commands/meeting_tracking_codes.py +233 -0
- wxcli/commands/meeting_transcripts.py +230 -0
- wxcli/commands/meetings.py +1997 -0
- wxcli/commands/memberships.py +164 -0
- wxcli/commands/messages.py +213 -0
- wxcli/commands/mode_management.py +244 -0
- wxcli/commands/my_call_settings.py +3577 -0
- wxcli/commands/numbers.py +405 -0
- wxcli/commands/operating_modes.py +410 -0
- wxcli/commands/org_contacts.py +284 -0
- wxcli/commands/org_health_cli.py +55 -0
- wxcli/commands/org_settings.py +71 -0
- wxcli/commands/organizations.py +83 -0
- wxcli/commands/paging_group.py +257 -0
- wxcli/commands/partner_admins.py +141 -0
- wxcli/commands/partner_reports.py +188 -0
- wxcli/commands/partner_tags.py +216 -0
- wxcli/commands/people.py +279 -0
- wxcli/commands/person_call_settings.py +71 -0
- wxcli/commands/pstn.py +280 -0
- wxcli/commands/recording_report.py +154 -0
- wxcli/commands/report_templates.py +38 -0
- wxcli/commands/reports.py +144 -0
- wxcli/commands/resource_group_memberships.py +160 -0
- wxcli/commands/resource_groups.py +67 -0
- wxcli/commands/roles.py +63 -0
- wxcli/commands/room_tabs.py +160 -0
- wxcli/commands/rooms.py +225 -0
- wxcli/commands/scim_bulk.py +46 -0
- wxcli/commands/scim_groups.py +246 -0
- wxcli/commands/scim_schemas.py +82 -0
- wxcli/commands/scim_users.py +283 -0
- wxcli/commands/security_audit.py +54 -0
- wxcli/commands/service_apps.py +51 -0
- wxcli/commands/single_number_reach.py +209 -0
- wxcli/commands/team_memberships.py +155 -0
- wxcli/commands/teams.py +153 -0
- wxcli/commands/ucm_profile.py +42 -0
- wxcli/commands/update.py +219 -0
- wxcli/commands/user_settings.py +5116 -0
- wxcli/commands/video_mesh.py +1090 -0
- wxcli/commands/virtual_extensions.py +502 -0
- wxcli/commands/virtual_line_settings.py +2072 -0
- wxcli/commands/webhooks.py +176 -0
- wxcli/commands/wholesale_billing_reports.py +147 -0
- wxcli/commands/wholesale_provisioning.py +523 -0
- wxcli/commands/workspace_locations.py +339 -0
- wxcli/commands/workspace_metrics.py +103 -0
- wxcli/commands/workspace_personalization.py +70 -0
- wxcli/commands/workspace_settings.py +3347 -0
- wxcli/commands/workspaces.py +279 -0
- wxcli/commands/xapi.py +114 -0
- wxcli/config.py +139 -0
- wxcli/errors.py +77 -0
- wxcli/main.py +210 -0
- wxcli/migration/CLAUDE.md +106 -0
- wxcli/migration/__init__.py +1 -0
- wxcli/migration/advisory/CLAUDE.md +196 -0
- wxcli/migration/advisory/__init__.py +46 -0
- wxcli/migration/advisory/advisor.py +107 -0
- wxcli/migration/advisory/advisory_patterns.py +2552 -0
- wxcli/migration/advisory/recommendation_rules.py +835 -0
- wxcli/migration/cucm/CLAUDE.md +74 -0
- wxcli/migration/cucm/__init__.py +5 -0
- wxcli/migration/cucm/connection.py +265 -0
- wxcli/migration/cucm/discovery.py +225 -0
- wxcli/migration/cucm/extractors/__init__.py +1 -0
- wxcli/migration/cucm/extractors/announcements.py +92 -0
- wxcli/migration/cucm/extractors/base.py +90 -0
- wxcli/migration/cucm/extractors/device_profiles.py +98 -0
- wxcli/migration/cucm/extractors/devices.py +193 -0
- wxcli/migration/cucm/extractors/e911.py +103 -0
- wxcli/migration/cucm/extractors/features.py +309 -0
- wxcli/migration/cucm/extractors/helpers.py +73 -0
- wxcli/migration/cucm/extractors/informational.py +295 -0
- wxcli/migration/cucm/extractors/locations.py +221 -0
- wxcli/migration/cucm/extractors/moh.py +100 -0
- wxcli/migration/cucm/extractors/remote_destinations.py +96 -0
- wxcli/migration/cucm/extractors/routing.py +493 -0
- wxcli/migration/cucm/extractors/shared_lines.py +146 -0
- wxcli/migration/cucm/extractors/templates.py +229 -0
- wxcli/migration/cucm/extractors/tier4.py +173 -0
- wxcli/migration/cucm/extractors/users.py +240 -0
- wxcli/migration/cucm/extractors/voicemail.py +243 -0
- wxcli/migration/cucm/extractors/workspaces.py +88 -0
- wxcli/migration/cucm/unity_connection.py +304 -0
- wxcli/migration/execute/CLAUDE.md +372 -0
- wxcli/migration/execute/__init__.py +303 -0
- wxcli/migration/execute/batch.py +277 -0
- wxcli/migration/execute/dependency.py +483 -0
- wxcli/migration/execute/engine.py +964 -0
- wxcli/migration/execute/handlers.py +2232 -0
- wxcli/migration/execute/planner.py +1939 -0
- wxcli/migration/execute/runtime.py +571 -0
- wxcli/migration/export/CLAUDE.md +49 -0
- wxcli/migration/export/__init__.py +8 -0
- wxcli/migration/export/csv_export.py +126 -0
- wxcli/migration/export/deployment_plan.py +479 -0
- wxcli/migration/export/json_export.py +60 -0
- wxcli/migration/models.py +941 -0
- wxcli/migration/phone_models.py +50 -0
- wxcli/migration/preflight/CLAUDE.md +52 -0
- wxcli/migration/preflight/__init__.py +89 -0
- wxcli/migration/preflight/checks.py +911 -0
- wxcli/migration/preflight/runner.py +293 -0
- wxcli/migration/rate_limiter.py +144 -0
- wxcli/migration/report/CLAUDE.md +245 -0
- wxcli/migration/report/__init__.py +7 -0
- wxcli/migration/report/appendix.py +2437 -0
- wxcli/migration/report/assembler.py +183 -0
- wxcli/migration/report/charts.py +263 -0
- wxcli/migration/report/executive.py +776 -0
- wxcli/migration/report/explainer.py +704 -0
- wxcli/migration/report/helpers.py +74 -0
- wxcli/migration/report/ingest.py +152 -0
- wxcli/migration/report/notice_templates.py +192 -0
- wxcli/migration/report/score.py +375 -0
- wxcli/migration/report/styles.py +1052 -0
- wxcli/migration/report/user_diff.py +1052 -0
- wxcli/migration/report/user_notice.py +425 -0
- wxcli/migration/state.py +139 -0
- wxcli/migration/store.py +860 -0
- wxcli/migration/transform/CLAUDE.md +107 -0
- wxcli/migration/transform/__init__.py +12 -0
- wxcli/migration/transform/analysis_pipeline.py +453 -0
- wxcli/migration/transform/analyzers/__init__.py +142 -0
- wxcli/migration/transform/analyzers/css_permission.py +189 -0
- wxcli/migration/transform/analyzers/css_routing.py +335 -0
- wxcli/migration/transform/analyzers/device_compatibility.py +122 -0
- wxcli/migration/transform/analyzers/dn_ambiguity.py +131 -0
- wxcli/migration/transform/analyzers/duplicate_user.py +221 -0
- wxcli/migration/transform/analyzers/extension_conflict.py +173 -0
- wxcli/migration/transform/analyzers/feature_approximation.py +227 -0
- wxcli/migration/transform/analyzers/layout_overflow.py +174 -0
- wxcli/migration/transform/analyzers/location_ambiguity.py +184 -0
- wxcli/migration/transform/analyzers/missing_data.py +300 -0
- wxcli/migration/transform/analyzers/selective_call_handling.py +394 -0
- wxcli/migration/transform/analyzers/shared_line.py +170 -0
- wxcli/migration/transform/analyzers/voicemail_compatibility.py +267 -0
- wxcli/migration/transform/analyzers/workspace_license.py +212 -0
- wxcli/migration/transform/cross_reference.py +1136 -0
- wxcli/migration/transform/cucm_pattern.py +246 -0
- wxcli/migration/transform/decisions.py +301 -0
- wxcli/migration/transform/e164.py +145 -0
- wxcli/migration/transform/engine.py +256 -0
- wxcli/migration/transform/mappers/CLAUDE.md +123 -0
- wxcli/migration/transform/mappers/__init__.py +76 -0
- wxcli/migration/transform/mappers/announcement_mapper.py +122 -0
- wxcli/migration/transform/mappers/base.py +190 -0
- wxcli/migration/transform/mappers/button_template_mapper.py +186 -0
- wxcli/migration/transform/mappers/call_forwarding_mapper.py +228 -0
- wxcli/migration/transform/mappers/call_settings_mapper.py +144 -0
- wxcli/migration/transform/mappers/css_mapper.py +868 -0
- wxcli/migration/transform/mappers/dect_mapper.py +367 -0
- wxcli/migration/transform/mappers/device_layout_mapper.py +289 -0
- wxcli/migration/transform/mappers/device_mapper.py +227 -0
- wxcli/migration/transform/mappers/device_profile_mapper.py +190 -0
- wxcli/migration/transform/mappers/device_settings_mapper.py +464 -0
- wxcli/migration/transform/mappers/e911_mapper.py +129 -0
- wxcli/migration/transform/mappers/ecbn_mapper.py +263 -0
- wxcli/migration/transform/mappers/executive_assistant_mapper.py +156 -0
- wxcli/migration/transform/mappers/feature_mapper.py +1179 -0
- wxcli/migration/transform/mappers/line_mapper.py +310 -0
- wxcli/migration/transform/mappers/location_mapper.py +235 -0
- wxcli/migration/transform/mappers/moh_mapper.py +114 -0
- wxcli/migration/transform/mappers/monitoring_mapper.py +221 -0
- wxcli/migration/transform/mappers/receptionist_mapper.py +245 -0
- wxcli/migration/transform/mappers/routing_mapper.py +821 -0
- wxcli/migration/transform/mappers/snr_mapper.py +201 -0
- wxcli/migration/transform/mappers/softkey_mapper.py +205 -0
- wxcli/migration/transform/mappers/user_mapper.py +264 -0
- wxcli/migration/transform/mappers/voicemail_group_mapper.py +286 -0
- wxcli/migration/transform/mappers/voicemail_mapper.py +514 -0
- wxcli/migration/transform/mappers/workspace_mapper.py +464 -0
- wxcli/migration/transform/normalizers.py +2008 -0
- wxcli/migration/transform/pattern_converter.py +106 -0
- wxcli/migration/transform/pipeline.py +271 -0
- wxcli/migration/transform/rules.py +255 -0
- wxcli/org_health/CLAUDE.md +63 -0
- wxcli/org_health/__init__.py +7 -0
- wxcli/org_health/analyze.py +93 -0
- wxcli/org_health/checks.py +458 -0
- wxcli/org_health/collector.py +62 -0
- wxcli/org_health/models.py +78 -0
- wxcli/org_health/report.py +300 -0
- wxcli/output.py +101 -0
- wxcli-1.2.0.dist-info/METADATA +505 -0
- wxcli-1.2.0.dist-info/RECORD +413 -0
- wxcli-1.2.0.dist-info/WHEEL +5 -0
- wxcli-1.2.0.dist-info/entry_points.txt +2 -0
- wxcli-1.2.0.dist-info/licenses/LICENSE +191 -0
- wxcli-1.2.0.dist-info/scm_file_list.json +751 -0
- wxcli-1.2.0.dist-info/scm_version.json +8 -0
- wxcli-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Feature extractors for CUCM AXL.
|
|
2
|
+
|
|
3
|
+
Pulls Hunt Pilots, Hunt Lists, Line Groups, CTI Route Points, Call Park,
|
|
4
|
+
Pickup Groups, Time Schedules, and Time Periods.
|
|
5
|
+
|
|
6
|
+
Sources:
|
|
7
|
+
- 02b-cucm-extraction.md §2.5 (feature extraction)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from wxcli.migration.cucm.connection import AXLConnection
|
|
16
|
+
from wxcli.migration.cucm.extractors.base import BaseExtractor, ExtractionResult
|
|
17
|
+
from wxcli.migration.cucm.extractors.helpers import ref_value, to_list
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# AXL Gotchas (discovered test bed expansion 2026-03-24):
|
|
22
|
+
# - addCallPickupGroup with <members>/<directoryNumber> fails on CUCM 15.0
|
|
23
|
+
# (null priority FK constraint). Use empty create + updateLine workaround.
|
|
24
|
+
# - No listPagingGroup/getPagingGroup AXL methods exist. Paging requires
|
|
25
|
+
# InformaCast/Cisco Paging Server. CanonicalPagingGroup is manual-only.
|
|
26
|
+
# - CTI Route Points may require SCCP instead of SIP on some device pools.
|
|
27
|
+
# - TimePeriod monthOfYear: 3-letter abbreviations only (Dec, Jan, etc.)
|
|
28
|
+
|
|
29
|
+
# ------------------------------------------------------------------
|
|
30
|
+
# ReturnedTags constants
|
|
31
|
+
# ------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
# Verified: listHuntPilot accepts these fields per WSDL LHuntPilot schema.
|
|
34
|
+
# Fields like mohSourceId, maxCallersInQueue, huntTimerCallPick, overflowDestination,
|
|
35
|
+
# enabled are only on getHuntPilot.
|
|
36
|
+
HUNT_PILOT_LIST_RETURNED_TAGS = {
|
|
37
|
+
"pattern": "", "description": "", "routePartitionName": "",
|
|
38
|
+
"huntListName": "",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Verified: list operations do NOT accept nested fields (members, lines).
|
|
42
|
+
# Use list tags for discovery, get operations for full detail.
|
|
43
|
+
HUNT_LIST_LIST_RETURNED_TAGS = {
|
|
44
|
+
"name": "", "description": "",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
LINE_GROUP_LIST_RETURNED_TAGS = {
|
|
48
|
+
"name": "", "distributionAlgorithm": "",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
CTI_RP_LIST_RETURNED_TAGS = {
|
|
52
|
+
"name": "", "description": "", "devicePoolName": "", "callingSearchSpaceName": "",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
CALL_PARK_RETURNED_TAGS = {
|
|
56
|
+
"pattern": "", "description": "", "routePartitionName": "",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
PICKUP_GROUP_LIST_RETURNED_TAGS = {
|
|
60
|
+
"name": "",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
TIME_SCHEDULE_LIST_RETURNED_TAGS = {
|
|
64
|
+
"name": "",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
TIME_PERIOD_LIST_RETURNED_TAGS = {
|
|
68
|
+
"name": "",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class FeatureExtractor(BaseExtractor):
|
|
73
|
+
"""Extract calling features from CUCM AXL.
|
|
74
|
+
|
|
75
|
+
Covers Hunt Pilots, Hunt Lists, Line Groups, CTI Route Points,
|
|
76
|
+
Call Park, Pickup Groups, Time Schedules, and Time Periods.
|
|
77
|
+
|
|
78
|
+
(from 02b §2.5)
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
name = "features"
|
|
82
|
+
|
|
83
|
+
def __init__(self, connection: AXLConnection) -> None:
|
|
84
|
+
super().__init__(connection)
|
|
85
|
+
self.results: dict[str, list[dict[str, Any]]] = {}
|
|
86
|
+
|
|
87
|
+
def extract(self) -> ExtractionResult:
|
|
88
|
+
"""Run all feature extractions.
|
|
89
|
+
|
|
90
|
+
Returns an ExtractionResult summarizing total objects and errors.
|
|
91
|
+
"""
|
|
92
|
+
result = ExtractionResult(extractor=self.name)
|
|
93
|
+
|
|
94
|
+
self.results["hunt_pilots"] = self._extract_hunt_pilots(result)
|
|
95
|
+
self.results["hunt_lists"] = self._extract_hunt_lists(result)
|
|
96
|
+
self.results["line_groups"] = self._extract_line_groups(result)
|
|
97
|
+
self.results["cti_route_points"] = self._extract_cti_route_points(result)
|
|
98
|
+
self.results["call_parks"] = self._extract_call_parks(result)
|
|
99
|
+
self.results["pickup_groups"] = self._extract_pickup_groups(result)
|
|
100
|
+
self.results["time_schedules"] = self._extract_time_schedules(result)
|
|
101
|
+
self.results["time_periods"] = self._extract_time_periods(result)
|
|
102
|
+
self.results["executive_assistant_pairs"] = self._extract_executive_assistant_pairs(result)
|
|
103
|
+
self.results["executive_settings"] = self._extract_executive_settings(result)
|
|
104
|
+
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
# ------------------------------------------------------------------
|
|
108
|
+
# Common list+get pattern
|
|
109
|
+
# ------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
def _list_and_get(
|
|
112
|
+
self,
|
|
113
|
+
result: ExtractionResult,
|
|
114
|
+
list_method: str,
|
|
115
|
+
get_method: str,
|
|
116
|
+
search_criteria: dict[str, str],
|
|
117
|
+
returned_tags: dict[str, str],
|
|
118
|
+
name_field: str = "name",
|
|
119
|
+
) -> list[dict[str, Any]]:
|
|
120
|
+
"""Common pattern: paginated list then per-object get.
|
|
121
|
+
|
|
122
|
+
On get failure (exception or None), the object is skipped and counted
|
|
123
|
+
as failed — consistent with DeviceExtractor and RoutingExtractor.
|
|
124
|
+
"""
|
|
125
|
+
items: list[dict[str, Any]] = []
|
|
126
|
+
try:
|
|
127
|
+
summaries = self.paginated_list(list_method, search_criteria, returned_tags)
|
|
128
|
+
except Exception as exc:
|
|
129
|
+
msg = f"{list_method} failed: {exc}"
|
|
130
|
+
logger.error("[%s] %s", self.name, msg)
|
|
131
|
+
result.errors.append(msg)
|
|
132
|
+
return items
|
|
133
|
+
|
|
134
|
+
for summary in summaries:
|
|
135
|
+
result.total += 1
|
|
136
|
+
obj_name = ref_value(summary.get(name_field)) or summary.get(name_field, "")
|
|
137
|
+
if not obj_name:
|
|
138
|
+
result.failed += 1
|
|
139
|
+
result.errors.append(f"{list_method} returned object with no {name_field}")
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
kwargs = {name_field: obj_name}
|
|
144
|
+
# Hunt pilots are looked up by pattern + partition, not name
|
|
145
|
+
if name_field == "pattern":
|
|
146
|
+
partition = ref_value(summary.get("routePartitionName"))
|
|
147
|
+
kwargs["routePartitionName"] = partition or ""
|
|
148
|
+
detail = self.get_detail(get_method, **kwargs)
|
|
149
|
+
except Exception as exc:
|
|
150
|
+
result.failed += 1
|
|
151
|
+
msg = f"{get_method}({obj_name}): {exc}"
|
|
152
|
+
logger.warning("[%s] %s", self.name, msg)
|
|
153
|
+
result.errors.append(msg)
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
if detail is None:
|
|
157
|
+
result.failed += 1
|
|
158
|
+
result.errors.append(f"{get_method} returned None for {obj_name}")
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
items.append(detail)
|
|
162
|
+
|
|
163
|
+
return items
|
|
164
|
+
|
|
165
|
+
# ------------------------------------------------------------------
|
|
166
|
+
# Hunt Pilots
|
|
167
|
+
# ------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
def _extract_hunt_pilots(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
170
|
+
"""Extract hunt pilots. Verified against live CUCM 15.0 2026-03-23."""
|
|
171
|
+
return self._list_and_get(
|
|
172
|
+
result, "listHuntPilot", "getHuntPilot",
|
|
173
|
+
{"pattern": "%"}, HUNT_PILOT_LIST_RETURNED_TAGS, name_field="pattern",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# ------------------------------------------------------------------
|
|
177
|
+
# Hunt Lists
|
|
178
|
+
# ------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
def _extract_hunt_lists(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
181
|
+
"""Extract hunt lists. Verified via pipeline execution against CUCM 15.0 testbed."""
|
|
182
|
+
return self._list_and_get(
|
|
183
|
+
result, "listHuntList", "getHuntList",
|
|
184
|
+
{"name": "%"}, HUNT_LIST_LIST_RETURNED_TAGS,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# ------------------------------------------------------------------
|
|
188
|
+
# Line Groups
|
|
189
|
+
# ------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
def _extract_line_groups(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
192
|
+
"""Extract line groups. Verified via pipeline execution against CUCM 15.0 testbed."""
|
|
193
|
+
return self._list_and_get(
|
|
194
|
+
result, "listLineGroup", "getLineGroup",
|
|
195
|
+
{"name": "%"}, LINE_GROUP_LIST_RETURNED_TAGS,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# ------------------------------------------------------------------
|
|
199
|
+
# CTI Route Points
|
|
200
|
+
# ------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
def _extract_cti_route_points(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
203
|
+
"""Extract CTI route points. Verified via pipeline execution against CUCM 15.0 testbed."""
|
|
204
|
+
return self._list_and_get(
|
|
205
|
+
result, "listCtiRoutePoint", "getCtiRoutePoint",
|
|
206
|
+
{"name": "%"}, CTI_RP_LIST_RETURNED_TAGS,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# ------------------------------------------------------------------
|
|
210
|
+
# Call Parks — list only, no get needed
|
|
211
|
+
# ------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
def _extract_call_parks(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
214
|
+
"""Extract call parks (list only, no get needed). Verified via pipeline execution."""
|
|
215
|
+
try:
|
|
216
|
+
parks = self.paginated_list(
|
|
217
|
+
"listCallPark", {"pattern": "%"}, CALL_PARK_RETURNED_TAGS,
|
|
218
|
+
)
|
|
219
|
+
result.total += len(parks)
|
|
220
|
+
return parks
|
|
221
|
+
except Exception as exc:
|
|
222
|
+
msg = f"listCallPark failed: {exc}"
|
|
223
|
+
logger.error("[%s] %s", self.name, msg)
|
|
224
|
+
result.errors.append(msg)
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
# ------------------------------------------------------------------
|
|
228
|
+
# Pickup Groups
|
|
229
|
+
# ------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
def _extract_pickup_groups(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
232
|
+
"""Extract pickup groups. Verified against live CUCM 15.0 2026-03-24."""
|
|
233
|
+
return self._list_and_get(
|
|
234
|
+
result, "listCallPickupGroup", "getCallPickupGroup",
|
|
235
|
+
{"pattern": "%"}, PICKUP_GROUP_LIST_RETURNED_TAGS,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# ------------------------------------------------------------------
|
|
239
|
+
# Time Schedules
|
|
240
|
+
# ------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
def _extract_time_schedules(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
243
|
+
"""Extract time schedules. Verified via pipeline execution against CUCM 15.0 testbed."""
|
|
244
|
+
return self._list_and_get(
|
|
245
|
+
result, "listTimeSchedule", "getTimeSchedule",
|
|
246
|
+
{"name": "%"}, TIME_SCHEDULE_LIST_RETURNED_TAGS,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# ------------------------------------------------------------------
|
|
250
|
+
# Time Periods
|
|
251
|
+
# ------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
def _extract_time_periods(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
254
|
+
"""Extract time periods. Verified via pipeline execution against CUCM 15.0 testbed."""
|
|
255
|
+
return self._list_and_get(
|
|
256
|
+
result, "listTimePeriod", "getTimePeriod",
|
|
257
|
+
{"name": "%"}, TIME_PERIOD_LIST_RETURNED_TAGS,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# ------------------------------------------------------------------
|
|
261
|
+
# Executive/Assistant Pairs (SQL query)
|
|
262
|
+
# ------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
def _extract_executive_assistant_pairs(
|
|
265
|
+
self, result: ExtractionResult,
|
|
266
|
+
) -> list[dict[str, Any]]:
|
|
267
|
+
"""Extract executive/assistant pairings via SQL query."""
|
|
268
|
+
try:
|
|
269
|
+
rows = self.conn.execute_sql(
|
|
270
|
+
"SELECT "
|
|
271
|
+
"exec_user.userid AS executive_userid, "
|
|
272
|
+
"asst_user.userid AS assistant_userid, "
|
|
273
|
+
"ea.fkexecutive AS executive_pkid, "
|
|
274
|
+
"ea.fkassistant AS assistant_pkid "
|
|
275
|
+
"FROM executiveassistant ea "
|
|
276
|
+
"JOIN enduser exec_user ON exec_user.pkid = ea.fkexecutive "
|
|
277
|
+
"JOIN enduser asst_user ON asst_user.pkid = ea.fkassistant"
|
|
278
|
+
)
|
|
279
|
+
except Exception as exc:
|
|
280
|
+
msg = f"executiveassistant SQL query failed: {exc}"
|
|
281
|
+
logger.warning("[%s] %s", self.name, msg)
|
|
282
|
+
result.errors.append(msg)
|
|
283
|
+
return []
|
|
284
|
+
result.total += len(rows)
|
|
285
|
+
return rows
|
|
286
|
+
|
|
287
|
+
# ------------------------------------------------------------------
|
|
288
|
+
# Executive/Assistant Settings (SQL query)
|
|
289
|
+
# ------------------------------------------------------------------
|
|
290
|
+
|
|
291
|
+
def _extract_executive_settings(
|
|
292
|
+
self, result: ExtractionResult,
|
|
293
|
+
) -> list[dict[str, Any]]:
|
|
294
|
+
"""Extract executive/assistant service subscriptions via SQL query."""
|
|
295
|
+
try:
|
|
296
|
+
rows = self.conn.execute_sql(
|
|
297
|
+
"SELECT eu.userid, s.name AS service_name, s.servicetype "
|
|
298
|
+
"FROM enduser eu "
|
|
299
|
+
"JOIN endusersubscribedservice euss ON euss.fkenduser = eu.pkid "
|
|
300
|
+
"JOIN subscribedservice s ON s.pkid = euss.fksubscribedservice "
|
|
301
|
+
"WHERE s.name IN ('Executive', 'Executive-Assistant')"
|
|
302
|
+
)
|
|
303
|
+
except Exception as exc:
|
|
304
|
+
msg = f"executive settings SQL query failed: {exc}"
|
|
305
|
+
logger.warning("[%s] %s", self.name, msg)
|
|
306
|
+
result.errors.append(msg)
|
|
307
|
+
return []
|
|
308
|
+
result.total += len(rows)
|
|
309
|
+
return rows
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Zeep dict navigation helpers for AXL response parsing.
|
|
2
|
+
|
|
3
|
+
AXL reference fields consistently use the ``{'_value_1': value, 'uuid': uuid}``
|
|
4
|
+
pattern. List fields may be a bare dict (single item) or a list.
|
|
5
|
+
|
|
6
|
+
Sources:
|
|
7
|
+
- 02b-cucm-extraction.md §3 (zeep dict navigation helpers)
|
|
8
|
+
- 02-normalization-architecture.md pass 1 example (reference field pattern)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ref_value(field: dict[str, Any] | str | None) -> str | None:
|
|
17
|
+
"""Extract the display value from a zeep reference field.
|
|
18
|
+
|
|
19
|
+
Zeep reference pattern: ``{'_value_1': 'DP-HQ', 'uuid': '{...}'}``
|
|
20
|
+
Returns ``'DP-HQ'``. Also handles plain strings and None.
|
|
21
|
+
(from 02b §3)
|
|
22
|
+
"""
|
|
23
|
+
if field is None:
|
|
24
|
+
return None
|
|
25
|
+
if isinstance(field, str):
|
|
26
|
+
return field
|
|
27
|
+
if isinstance(field, dict):
|
|
28
|
+
return field.get("_value_1")
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def ref_uuid(field: dict[str, Any] | None) -> str | None:
|
|
33
|
+
"""Extract the UUID from a zeep reference field.
|
|
34
|
+
|
|
35
|
+
(from 02b §3)
|
|
36
|
+
"""
|
|
37
|
+
if isinstance(field, dict):
|
|
38
|
+
return field.get("uuid")
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def to_list(field: dict[str, Any] | list[Any] | None, key: str) -> list[Any]:
|
|
43
|
+
"""Normalize a zeep list field to a Python list.
|
|
44
|
+
|
|
45
|
+
Zeep list pattern:
|
|
46
|
+
``{'line': [item1, item2, ...]}`` — multiple items
|
|
47
|
+
``{'line': item1}`` — single item (not wrapped in list)
|
|
48
|
+
``None`` — empty
|
|
49
|
+
|
|
50
|
+
(from 02b §3)
|
|
51
|
+
"""
|
|
52
|
+
if field is None:
|
|
53
|
+
return []
|
|
54
|
+
if isinstance(field, dict):
|
|
55
|
+
inner = field.get(key)
|
|
56
|
+
if inner is None:
|
|
57
|
+
return []
|
|
58
|
+
if isinstance(inner, list):
|
|
59
|
+
return inner
|
|
60
|
+
return [inner]
|
|
61
|
+
if isinstance(field, list):
|
|
62
|
+
return field
|
|
63
|
+
return []
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def str_to_bool(value: str | None) -> bool | None:
|
|
67
|
+
"""Convert AXL string boolean to Python bool.
|
|
68
|
+
|
|
69
|
+
(from 02b §3)
|
|
70
|
+
"""
|
|
71
|
+
if value is None:
|
|
72
|
+
return None
|
|
73
|
+
return str(value).lower() == "true"
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""Tier 3: Informational extraction — CUCM objects with no Webex equivalent.
|
|
2
|
+
|
|
3
|
+
Extracted for assessment report only. No canonical types, no mappers.
|
|
4
|
+
Objects stored as MigrationObject with object_type="info_{type_name}".
|
|
5
|
+
|
|
6
|
+
20 types across 4 categories:
|
|
7
|
+
- cloud_managed (7): Regions, SRST, MRG, MRL, AAR, Device Mobility, Conference Bridges
|
|
8
|
+
- not_migratable (3): Softkey Templates, IP Phone Services, Intercom DNs
|
|
9
|
+
- different_arch (6): Common Phone Config, Phone Button Templates, Feature Control,
|
|
10
|
+
Credential Policies, Recording Profiles, LDAP Directories
|
|
11
|
+
- planning (4): Application Users, H.323 Gateways, Enterprise Params, Service Params
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from wxcli.migration.cucm.extractors.base import BaseExtractor, ExtractionResult
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# (suffix, axl_list_method, search_criteria, returned_tags, category)
|
|
24
|
+
# These 16 types use standard paginated_list().
|
|
25
|
+
INFORMATIONAL_TYPES: list[tuple[str, str, dict, dict, str]] = [
|
|
26
|
+
# Category 1: Cloud-managed
|
|
27
|
+
("region", "listRegion", {"name": "%"}, {"name": "", "defaultCodec": ""}, "cloud_managed"),
|
|
28
|
+
("srst", "listSrst", {"name": "%"}, {"name": "", "ipAddress": "", "port": ""}, "cloud_managed"),
|
|
29
|
+
("media_resource_group", "listMediaResourceGroup", {"name": "%"}, {"name": "", "description": ""}, "cloud_managed"),
|
|
30
|
+
("media_resource_list", "listMediaResourceList", {"name": "%"}, {"name": "", "description": ""}, "cloud_managed"),
|
|
31
|
+
("aar_group", "listAarGroup", {"name": "%"}, {"name": "", "description": ""}, "cloud_managed"),
|
|
32
|
+
("device_mobility_group", "listDeviceMobilityGroup", {"name": "%"}, {"name": "", "description": ""}, "cloud_managed"),
|
|
33
|
+
("conference_bridge", "listConferenceBridge", {"name": "%"}, {"name": "", "description": "", "product": ""}, "cloud_managed"),
|
|
34
|
+
# Category 2: Not migratable
|
|
35
|
+
("ip_phone_service", "listIpPhoneService", {"name": "%"}, {"name": "", "url": "", "serviceType": ""}, "not_migratable"),
|
|
36
|
+
# Category 3: Different architecture
|
|
37
|
+
("common_phone_config", "listCommonPhoneConfig", {"name": "%"}, {"name": "", "description": ""}, "different_arch"),
|
|
38
|
+
("phone_button_template", "listPhoneButtonTemplate", {"name": "%"}, {"name": ""}, "different_arch"),
|
|
39
|
+
("feature_control_policy", "listFeatureControlPolicy", {"name": "%"}, {"name": "", "description": ""}, "different_arch"),
|
|
40
|
+
("credential_policy", "listCredentialPolicy", {"name": "%"}, {"name": "", "description": ""}, "different_arch"),
|
|
41
|
+
("recording_profile", "listRecordingProfile", {"name": "%"}, {"name": "", "recorderDestination": ""}, "different_arch"),
|
|
42
|
+
("ldap_directory", "listLdapDirectory", {"name": "%"}, {"name": "", "ldapDn": ""}, "different_arch"),
|
|
43
|
+
# Category 4: Planning input
|
|
44
|
+
("app_user", "listAppUser", {"userid": "%"}, {"userid": "", "description": "", "associatedDevices": ""}, "planning"),
|
|
45
|
+
("h323_gateway", "listH323Gateway", {"name": "%"}, {"name": "", "description": "", "product": ""}, "planning"),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# Category metadata for report rendering
|
|
49
|
+
CATEGORY_METADATA: dict[str, dict[str, str]] = {
|
|
50
|
+
"cloud_managed": {
|
|
51
|
+
"label": "Cloud-Managed Resources",
|
|
52
|
+
"message": "No migration action needed — Webex manages this automatically.",
|
|
53
|
+
},
|
|
54
|
+
"not_migratable": {
|
|
55
|
+
"label": "Feature Gaps",
|
|
56
|
+
"message": "This CUCM feature has no Webex equivalent — functionality will be lost.",
|
|
57
|
+
},
|
|
58
|
+
"different_arch": {
|
|
59
|
+
"label": "Manual Reconfiguration Required",
|
|
60
|
+
"message": "Must be reconfigured manually in Webex Control Hub.",
|
|
61
|
+
},
|
|
62
|
+
"planning": {
|
|
63
|
+
"label": "Migration Planning Inputs",
|
|
64
|
+
"message": "Data informing migration planning decisions.",
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Friendly display names for each info type
|
|
69
|
+
DISPLAY_NAMES: dict[str, str] = {
|
|
70
|
+
"region": "Regions",
|
|
71
|
+
"srst": "SRST References",
|
|
72
|
+
"media_resource_group": "Media Resource Groups",
|
|
73
|
+
"media_resource_list": "Media Resource Lists",
|
|
74
|
+
"aar_group": "AAR Groups",
|
|
75
|
+
"device_mobility_group": "Device Mobility Groups",
|
|
76
|
+
"conference_bridge": "Conference Bridges",
|
|
77
|
+
"softkey_template": "Softkey Templates",
|
|
78
|
+
"ip_phone_service": "IP Phone Services",
|
|
79
|
+
"intercom": "Intercom DNs",
|
|
80
|
+
"common_phone_config": "Common Phone Profiles",
|
|
81
|
+
"phone_button_template": "Phone Button Templates",
|
|
82
|
+
"feature_control_policy": "Feature Control Policies",
|
|
83
|
+
"credential_policy": "Credential Policies",
|
|
84
|
+
"recording_profile": "Recording Profiles",
|
|
85
|
+
"ldap_directory": "LDAP Directories",
|
|
86
|
+
"app_user": "Application Users",
|
|
87
|
+
"h323_gateway": "H.323 Gateways",
|
|
88
|
+
"enterprise_params": "Enterprise Parameters",
|
|
89
|
+
"service_params": "Service Parameters",
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Webex equivalents for "different_arch" types
|
|
93
|
+
WEBEX_EQUIVALENTS: dict[str, str] = {
|
|
94
|
+
"common_phone_config": "Device configuration templates",
|
|
95
|
+
"phone_button_template": "Line key templates",
|
|
96
|
+
"feature_control_policy": "Calling policies (org/location level)",
|
|
97
|
+
"credential_policy": "SSO / password policies",
|
|
98
|
+
"recording_profile": "Call recording settings",
|
|
99
|
+
"ldap_directory": "Directory sync (Azure AD / Okta / SCIM)",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Workarounds for "not_migratable" types
|
|
103
|
+
NOT_MIGRATABLE_WORKAROUNDS: dict[str, str] = {
|
|
104
|
+
"softkey_template": "9800-series supports PSK; classic MPP uses Webex defaults",
|
|
105
|
+
"ip_phone_service": "Webex app replaces most XML service use cases",
|
|
106
|
+
"intercom": "Speed dial + auto-answer as workaround",
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class InformationalExtractor(BaseExtractor):
|
|
111
|
+
"""Extract 20 CUCM object types for assessment report only.
|
|
112
|
+
|
|
113
|
+
No canonical types, no mappers, no execution handlers.
|
|
114
|
+
Data flows: extractor -> store -> report appendix.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
name = "informational"
|
|
118
|
+
|
|
119
|
+
def __init__(self, connection) -> None:
|
|
120
|
+
super().__init__(connection)
|
|
121
|
+
self.results: dict[str, list[dict[str, Any]]] = {}
|
|
122
|
+
|
|
123
|
+
def extract(self) -> ExtractionResult:
|
|
124
|
+
"""Run extraction for all 20 informational types."""
|
|
125
|
+
result = ExtractionResult(extractor=self.name)
|
|
126
|
+
|
|
127
|
+
# 1. Standard AXL list types (16 types)
|
|
128
|
+
for suffix, method, criteria, tags, category in INFORMATIONAL_TYPES:
|
|
129
|
+
items = self._extract_list_type(suffix, method, criteria, tags, category, result)
|
|
130
|
+
self.results[suffix] = items
|
|
131
|
+
|
|
132
|
+
# Enrich common_phone_config with vendorConfig detail
|
|
133
|
+
cpcs = self.results.get("common_phone_config", [])
|
|
134
|
+
if cpcs:
|
|
135
|
+
self._fetch_common_phone_config_details(cpcs, result)
|
|
136
|
+
|
|
137
|
+
# 2. Softkey templates via SQL (AXL list doesn't exist)
|
|
138
|
+
self.results["softkey_template"] = self._extract_softkey_templates(result)
|
|
139
|
+
|
|
140
|
+
# 3. Intercom DNs via SQL
|
|
141
|
+
self.results["intercom"] = self._extract_intercom_dns(result)
|
|
142
|
+
|
|
143
|
+
# 4. Enterprise parameters (single getEnterprise call)
|
|
144
|
+
self.results["enterprise_params"] = self._extract_enterprise_params(result)
|
|
145
|
+
|
|
146
|
+
# 5. Service parameters (filtered listProcessConfig)
|
|
147
|
+
self.results["service_params"] = self._extract_service_params(result)
|
|
148
|
+
|
|
149
|
+
logger.info(
|
|
150
|
+
"[%s] Extracted %d informational objects across %d types (%d failed)",
|
|
151
|
+
self.name,
|
|
152
|
+
result.total,
|
|
153
|
+
sum(1 for v in self.results.values() if v),
|
|
154
|
+
result.failed,
|
|
155
|
+
)
|
|
156
|
+
return result
|
|
157
|
+
|
|
158
|
+
def _fetch_common_phone_config_details(
|
|
159
|
+
self, items: list[dict[str, Any]], result: ExtractionResult,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Enrich common_phone_config items with vendorConfig from getCommonPhoneConfig."""
|
|
162
|
+
for item in items:
|
|
163
|
+
name = item.get("name")
|
|
164
|
+
if not name:
|
|
165
|
+
continue
|
|
166
|
+
try:
|
|
167
|
+
resp = self.conn.service.getCommonPhoneConfig(name=name)
|
|
168
|
+
detail = resp
|
|
169
|
+
if isinstance(resp, dict):
|
|
170
|
+
detail = resp.get("return", resp)
|
|
171
|
+
if isinstance(detail, dict):
|
|
172
|
+
detail = detail.get("commonPhoneConfig", detail)
|
|
173
|
+
if isinstance(detail, dict):
|
|
174
|
+
vendor_config = detail.get("vendorConfig")
|
|
175
|
+
if vendor_config is not None:
|
|
176
|
+
item["vendorConfig"] = (
|
|
177
|
+
str(vendor_config) if not isinstance(vendor_config, str)
|
|
178
|
+
else vendor_config
|
|
179
|
+
)
|
|
180
|
+
for field in ("dndOption", "dndAlertingType"):
|
|
181
|
+
val = detail.get(field)
|
|
182
|
+
if val is not None:
|
|
183
|
+
item[field] = val
|
|
184
|
+
except Exception as exc:
|
|
185
|
+
logger.debug(
|
|
186
|
+
"[%s] getCommonPhoneConfig(%s) failed: %s", self.name, name, exc
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _extract_list_type(
|
|
190
|
+
self,
|
|
191
|
+
suffix: str,
|
|
192
|
+
method: str,
|
|
193
|
+
criteria: dict,
|
|
194
|
+
tags: dict,
|
|
195
|
+
category: str,
|
|
196
|
+
result: ExtractionResult,
|
|
197
|
+
) -> list[dict[str, Any]]:
|
|
198
|
+
"""Extract a single AXL list type."""
|
|
199
|
+
try:
|
|
200
|
+
items = self.paginated_list(method, criteria, tags)
|
|
201
|
+
except Exception as exc:
|
|
202
|
+
logger.warning("[%s] %s failed: %s", self.name, method, exc)
|
|
203
|
+
result.errors.append(f"{method} failed: {exc}")
|
|
204
|
+
result.failed += 1
|
|
205
|
+
return []
|
|
206
|
+
|
|
207
|
+
result.total += len(items)
|
|
208
|
+
for item in items:
|
|
209
|
+
item["_category"] = category
|
|
210
|
+
item["_info_type"] = suffix
|
|
211
|
+
return items
|
|
212
|
+
|
|
213
|
+
def _extract_softkey_templates(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
214
|
+
"""Extract softkey template names via SQL (AXL list doesn't exist in CUCM 15.0)."""
|
|
215
|
+
try:
|
|
216
|
+
rows = self.conn.execute_sql(
|
|
217
|
+
"SELECT name, description FROM softkeytemplate ORDER BY name"
|
|
218
|
+
)
|
|
219
|
+
except Exception as exc:
|
|
220
|
+
logger.warning("[%s] Softkey template SQL failed: %s", self.name, exc)
|
|
221
|
+
result.errors.append(f"Softkey template SQL failed: {exc}")
|
|
222
|
+
return []
|
|
223
|
+
|
|
224
|
+
result.total += len(rows)
|
|
225
|
+
for row in rows:
|
|
226
|
+
row["_category"] = "not_migratable"
|
|
227
|
+
row["_info_type"] = "softkey_template"
|
|
228
|
+
return rows
|
|
229
|
+
|
|
230
|
+
def _extract_intercom_dns(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
231
|
+
"""Extract intercom DNs via SQL query."""
|
|
232
|
+
try:
|
|
233
|
+
rows = self.conn.execute_sql(
|
|
234
|
+
"SELECT dnorpattern, description, fkroutepartition "
|
|
235
|
+
"FROM numplan WHERE tkpatternusage = 9 ORDER BY dnorpattern"
|
|
236
|
+
)
|
|
237
|
+
except Exception as exc:
|
|
238
|
+
logger.warning("[%s] Intercom DN SQL failed: %s", self.name, exc)
|
|
239
|
+
result.errors.append(f"Intercom DN SQL failed: {exc}")
|
|
240
|
+
return []
|
|
241
|
+
|
|
242
|
+
result.total += len(rows)
|
|
243
|
+
for row in rows:
|
|
244
|
+
row["_category"] = "not_migratable"
|
|
245
|
+
row["_info_type"] = "intercom"
|
|
246
|
+
return rows
|
|
247
|
+
|
|
248
|
+
def _extract_enterprise_params(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
249
|
+
"""Extract enterprise parameters via single getEnterprise call."""
|
|
250
|
+
try:
|
|
251
|
+
raw = self.conn.service.getEnterprise()
|
|
252
|
+
if raw is None:
|
|
253
|
+
return []
|
|
254
|
+
from zeep.helpers import serialize_object
|
|
255
|
+
params = serialize_object(raw, dict)
|
|
256
|
+
if not isinstance(params, dict):
|
|
257
|
+
params = {"raw": str(params)}
|
|
258
|
+
except Exception as exc:
|
|
259
|
+
logger.warning("[%s] getEnterprise failed: %s", self.name, exc)
|
|
260
|
+
result.errors.append(f"getEnterprise failed: {exc}")
|
|
261
|
+
return []
|
|
262
|
+
|
|
263
|
+
result.total += 1
|
|
264
|
+
params["_category"] = "planning"
|
|
265
|
+
params["_info_type"] = "enterprise_params"
|
|
266
|
+
params["name"] = "Enterprise Parameters"
|
|
267
|
+
return [params]
|
|
268
|
+
|
|
269
|
+
def _extract_service_params(self, result: ExtractionResult) -> list[dict[str, Any]]:
|
|
270
|
+
"""Extract telephony-related service parameters via listProcessConfig."""
|
|
271
|
+
try:
|
|
272
|
+
items = self.paginated_list(
|
|
273
|
+
"listProcessConfig",
|
|
274
|
+
{"name": "%"},
|
|
275
|
+
{"name": "", "service": "", "value": ""},
|
|
276
|
+
)
|
|
277
|
+
except Exception as exc:
|
|
278
|
+
logger.warning("[%s] listProcessConfig failed: %s", self.name, exc)
|
|
279
|
+
result.errors.append(f"listProcessConfig failed: {exc}")
|
|
280
|
+
return []
|
|
281
|
+
|
|
282
|
+
telephony_keywords = (
|
|
283
|
+
"callmanager", "telephony", "tftp", "certificate",
|
|
284
|
+
"cisco call", "cisco ip voice",
|
|
285
|
+
)
|
|
286
|
+
filtered = []
|
|
287
|
+
for item in items:
|
|
288
|
+
service = (item.get("service") or "").lower()
|
|
289
|
+
if any(kw in service for kw in telephony_keywords):
|
|
290
|
+
item["_category"] = "planning"
|
|
291
|
+
item["_info_type"] = "service_params"
|
|
292
|
+
filtered.append(item)
|
|
293
|
+
|
|
294
|
+
result.total += len(filtered)
|
|
295
|
+
return filtered
|