mtncloud-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.dockerignore +20 -0
- data/.github/pull_request_template.md +14 -0
- data/.github/workflows/ci.yml +56 -0
- data/.github/workflows/release.yml +46 -0
- data/.gitignore +25 -0
- data/CLAUDE.md +24 -0
- data/CONTRIBUTING.md +91 -0
- data/Dockerfile +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +187 -0
- data/Rakefile +26 -0
- data/bin/mcs +3 -0
- data/bin/mtncloud +27 -0
- data/lib/mtncloud/api/account_groups_interface.rb +48 -0
- data/lib/mtncloud/api/account_users_interface.rb +68 -0
- data/lib/mtncloud/api/accounts_interface.rb +9 -0
- data/lib/mtncloud/api/activity_interface.rb +9 -0
- data/lib/mtncloud/api/affinity_groups_interface.rb +46 -0
- data/lib/mtncloud/api/api_client.rb +1091 -0
- data/lib/mtncloud/api/appliance_settings_interface.rb +49 -0
- data/lib/mtncloud/api/approvals_interface.rb +32 -0
- data/lib/mtncloud/api/apps_interface.rb +180 -0
- data/lib/mtncloud/api/archive_buckets_interface.rb +116 -0
- data/lib/mtncloud/api/archive_files_interface.rb +173 -0
- data/lib/mtncloud/api/audit_interface.rb +9 -0
- data/lib/mtncloud/api/auth_interface.rb +58 -0
- data/lib/mtncloud/api/backup_jobs_interface.rb +13 -0
- data/lib/mtncloud/api/backup_restores_interface.rb +27 -0
- data/lib/mtncloud/api/backup_results_interface.rb +32 -0
- data/lib/mtncloud/api/backup_service_types_interface.rb +9 -0
- data/lib/mtncloud/api/backup_services_interface.rb +9 -0
- data/lib/mtncloud/api/backup_settings_interface.rb +20 -0
- data/lib/mtncloud/api/backup_types_interface.rb +9 -0
- data/lib/mtncloud/api/backups_interface.rb +21 -0
- data/lib/mtncloud/api/billing_interface.rb +34 -0
- data/lib/mtncloud/api/blueprints_interface.rb +70 -0
- data/lib/mtncloud/api/body_io.rb +22 -0
- data/lib/mtncloud/api/budgets_interface.rb +48 -0
- data/lib/mtncloud/api/catalog_item_types_interface.rb +29 -0
- data/lib/mtncloud/api/certificate_types_interface.rb +14 -0
- data/lib/mtncloud/api/certificates_interface.rb +9 -0
- data/lib/mtncloud/api/clients_interface.rb +41 -0
- data/lib/mtncloud/api/cloud_datastores_interface.rb +48 -0
- data/lib/mtncloud/api/cloud_folders_interface.rb +41 -0
- data/lib/mtncloud/api/cloud_policies_interface.rb +41 -0
- data/lib/mtncloud/api/cloud_resource_pools_interface.rb +66 -0
- data/lib/mtncloud/api/clouds_interface.rb +217 -0
- data/lib/mtncloud/api/clusters_interface.rb +471 -0
- data/lib/mtncloud/api/containers_interface.rb +145 -0
- data/lib/mtncloud/api/credential_types_interface.rb +9 -0
- data/lib/mtncloud/api/credentials_interface.rb +9 -0
- data/lib/mtncloud/api/custom_instance_types_interface.rb +99 -0
- data/lib/mtncloud/api/cypher_interface.rb +51 -0
- data/lib/mtncloud/api/dashboard_interface.rb +14 -0
- data/lib/mtncloud/api/datastores_interface.rb +21 -0
- data/lib/mtncloud/api/deploy_interface.rb +44 -0
- data/lib/mtncloud/api/deployments_interface.rb +79 -0
- data/lib/mtncloud/api/distributed_workers_interface.rb +41 -0
- data/lib/mtncloud/api/email_templates_interface.rb +53 -0
- data/lib/mtncloud/api/environments_interface.rb +48 -0
- data/lib/mtncloud/api/execute_schedules_interface.rb +71 -0
- data/lib/mtncloud/api/execution_request_interface.rb +27 -0
- data/lib/mtncloud/api/file_copy_request_interface.rb +68 -0
- data/lib/mtncloud/api/forgot_password_interface.rb +17 -0
- data/lib/mtncloud/api/group_policies_interface.rb +41 -0
- data/lib/mtncloud/api/groups_interface.rb +73 -0
- data/lib/mtncloud/api/guidance_interface.rb +44 -0
- data/lib/mtncloud/api/guidance_settings_interface.rb +17 -0
- data/lib/mtncloud/api/health_interface.rb +100 -0
- data/lib/mtncloud/api/host_vm_groups_interface.rb +37 -0
- data/lib/mtncloud/api/hub_interface.rb +25 -0
- data/lib/mtncloud/api/image_builder_boot_scripts_interface.rb +41 -0
- data/lib/mtncloud/api/image_builder_image_builds_interface.rb +73 -0
- data/lib/mtncloud/api/image_builder_interface.rb +20 -0
- data/lib/mtncloud/api/image_builder_preseed_scripts_interface.rb +41 -0
- data/lib/mtncloud/api/instance_types_interface.rb +20 -0
- data/lib/mtncloud/api/instances_interface.rb +453 -0
- data/lib/mtncloud/api/integration_types_interface.rb +14 -0
- data/lib/mtncloud/api/integrations_interface.rb +74 -0
- data/lib/mtncloud/api/invoice_line_items_interface.rb +9 -0
- data/lib/mtncloud/api/invoices_interface.rb +28 -0
- data/lib/mtncloud/api/jobs_interface.rb +83 -0
- data/lib/mtncloud/api/key_pairs_interface.rb +57 -0
- data/lib/mtncloud/api/library_cluster_layouts_interface.rb +47 -0
- data/lib/mtncloud/api/library_cluster_packages_interface.rb +60 -0
- data/lib/mtncloud/api/library_container_scripts_interface.rb +43 -0
- data/lib/mtncloud/api/library_container_templates_interface.rb +43 -0
- data/lib/mtncloud/api/library_container_types_interface.rb +59 -0
- data/lib/mtncloud/api/library_container_upgrades_interface.rb +60 -0
- data/lib/mtncloud/api/library_instance_types_interface.rb +74 -0
- data/lib/mtncloud/api/library_layouts_interface.rb +69 -0
- data/lib/mtncloud/api/library_operating_systems_interface.rb +63 -0
- data/lib/mtncloud/api/library_spec_template_types_interface.rb +43 -0
- data/lib/mtncloud/api/library_spec_templates_interface.rb +43 -0
- data/lib/mtncloud/api/license_interface.rb +30 -0
- data/lib/mtncloud/api/load_balancer_monitors_interface.rb +9 -0
- data/lib/mtncloud/api/load_balancer_pool_nodes_interface.rb +8 -0
- data/lib/mtncloud/api/load_balancer_pools_interface.rb +9 -0
- data/lib/mtncloud/api/load_balancer_pools_secondary_interface.rb +9 -0
- data/lib/mtncloud/api/load_balancer_profiles_interface.rb +9 -0
- data/lib/mtncloud/api/load_balancer_types_interface.rb +9 -0
- data/lib/mtncloud/api/load_balancer_virtual_servers_interface.rb +18 -0
- data/lib/mtncloud/api/load_balancers_interface.rb +14 -0
- data/lib/mtncloud/api/log_settings_interface.rb +44 -0
- data/lib/mtncloud/api/logs_interface.rb +37 -0
- data/lib/mtncloud/api/migrations_interface.rb +49 -0
- data/lib/mtncloud/api/monitoring_alerts_interface.rb +39 -0
- data/lib/mtncloud/api/monitoring_apps_interface.rb +69 -0
- data/lib/mtncloud/api/monitoring_checks_interface.rb +103 -0
- data/lib/mtncloud/api/monitoring_contacts_interface.rb +41 -0
- data/lib/mtncloud/api/monitoring_groups_interface.rb +77 -0
- data/lib/mtncloud/api/monitoring_incidents_interface.rb +107 -0
- data/lib/mtncloud/api/monitoring_interface.rb +35 -0
- data/lib/mtncloud/api/monitoring_settings_interface.rb +21 -0
- data/lib/mtncloud/api/network_dhcp_relays_interface.rb +36 -0
- data/lib/mtncloud/api/network_dhcp_servers_interface.rb +36 -0
- data/lib/mtncloud/api/network_domain_records_interface.rb +41 -0
- data/lib/mtncloud/api/network_domains_interface.rb +41 -0
- data/lib/mtncloud/api/network_edge_clusters_interface.rb +26 -0
- data/lib/mtncloud/api/network_floating_ips_interface.rb +40 -0
- data/lib/mtncloud/api/network_groups_interface.rb +41 -0
- data/lib/mtncloud/api/network_pool_ips_interface.rb +41 -0
- data/lib/mtncloud/api/network_pool_server_types_interface.rb +9 -0
- data/lib/mtncloud/api/network_pool_servers_interface.rb +48 -0
- data/lib/mtncloud/api/network_pools_interface.rb +41 -0
- data/lib/mtncloud/api/network_proxies_interface.rb +41 -0
- data/lib/mtncloud/api/network_routers_interface.rb +181 -0
- data/lib/mtncloud/api/network_security_server_types_interface.rb +9 -0
- data/lib/mtncloud/api/network_security_servers_interface.rb +27 -0
- data/lib/mtncloud/api/network_server_groups_interface.rb +7 -0
- data/lib/mtncloud/api/network_server_types_interface.rb +9 -0
- data/lib/mtncloud/api/network_servers_interface.rb +102 -0
- data/lib/mtncloud/api/network_services_interface.rb +63 -0
- data/lib/mtncloud/api/network_static_routes_interface.rb +36 -0
- data/lib/mtncloud/api/network_subnets_interface.rb +42 -0
- data/lib/mtncloud/api/network_types_interface.rb +20 -0
- data/lib/mtncloud/api/networks_interface.rb +48 -0
- data/lib/mtncloud/api/option_type_forms_interface.rb +9 -0
- data/lib/mtncloud/api/option_type_lists_interface.rb +53 -0
- data/lib/mtncloud/api/option_types_interface.rb +41 -0
- data/lib/mtncloud/api/options_interface.rb +17 -0
- data/lib/mtncloud/api/packages_interface.rb +112 -0
- data/lib/mtncloud/api/ping_interface.rb +22 -0
- data/lib/mtncloud/api/plugins_interface.rb +22 -0
- data/lib/mtncloud/api/policies_interface.rb +56 -0
- data/lib/mtncloud/api/power_schedules_interface.rb +71 -0
- data/lib/mtncloud/api/price_sets_interface.rb +39 -0
- data/lib/mtncloud/api/prices_interface.rb +71 -0
- data/lib/mtncloud/api/processes_interface.rb +40 -0
- data/lib/mtncloud/api/projects_interface.rb +33 -0
- data/lib/mtncloud/api/provision_types_interface.rb +24 -0
- data/lib/mtncloud/api/provisioning_license_types_interface.rb +31 -0
- data/lib/mtncloud/api/provisioning_licenses_interface.rb +60 -0
- data/lib/mtncloud/api/provisioning_settings_interface.rb +26 -0
- data/lib/mtncloud/api/read_interface.rb +24 -0
- data/lib/mtncloud/api/reports_interface.rb +70 -0
- data/lib/mtncloud/api/resource_pool_groups_interface.rb +51 -0
- data/lib/mtncloud/api/rest_interface.rb +37 -0
- data/lib/mtncloud/api/roles_interface.rb +151 -0
- data/lib/mtncloud/api/scale_thresholds_interface.rb +9 -0
- data/lib/mtncloud/api/search_interface.rb +13 -0
- data/lib/mtncloud/api/secondary_read_interface.rb +25 -0
- data/lib/mtncloud/api/secondary_rest_interface.rb +42 -0
- data/lib/mtncloud/api/security_group_rules_interface.rb +35 -0
- data/lib/mtncloud/api/security_groups_interface.rb +56 -0
- data/lib/mtncloud/api/security_package_types_interface.rb +9 -0
- data/lib/mtncloud/api/security_packages_interface.rb +9 -0
- data/lib/mtncloud/api/security_scans_interface.rb +9 -0
- data/lib/mtncloud/api/server_devices_interface.rb +30 -0
- data/lib/mtncloud/api/server_types_interface.rb +20 -0
- data/lib/mtncloud/api/servers_interface.rb +230 -0
- data/lib/mtncloud/api/service_catalog_interface.rb +89 -0
- data/lib/mtncloud/api/service_plans_interface.rb +62 -0
- data/lib/mtncloud/api/setup_interface.rb +46 -0
- data/lib/mtncloud/api/snapshots_interface.rb +19 -0
- data/lib/mtncloud/api/storage_datastores_interface.rb +44 -0
- data/lib/mtncloud/api/storage_providers_interface.rb +151 -0
- data/lib/mtncloud/api/storage_server_types_interface.rb +14 -0
- data/lib/mtncloud/api/storage_servers_interface.rb +9 -0
- data/lib/mtncloud/api/storage_volume_types_interface.rb +9 -0
- data/lib/mtncloud/api/storage_volumes_interface.rb +24 -0
- data/lib/mtncloud/api/subnet_types_interface.rb +20 -0
- data/lib/mtncloud/api/subnets_interface.rb +41 -0
- data/lib/mtncloud/api/support_bundles_interface.rb +46 -0
- data/lib/mtncloud/api/system_types_interface.rb +13 -0
- data/lib/mtncloud/api/systems_interface.rb +55 -0
- data/lib/mtncloud/api/task_sets_interface.rb +52 -0
- data/lib/mtncloud/api/tasks_interface.rb +66 -0
- data/lib/mtncloud/api/tokens_interface.rb +39 -0
- data/lib/mtncloud/api/usage_interface.rb +18 -0
- data/lib/mtncloud/api/user_groups_interface.rb +59 -0
- data/lib/mtncloud/api/user_settings_interface.rb +90 -0
- data/lib/mtncloud/api/user_sources_interface.rb +99 -0
- data/lib/mtncloud/api/users_interface.rb +33 -0
- data/lib/mtncloud/api/vdi_allocations_interface.rb +9 -0
- data/lib/mtncloud/api/vdi_apps_interface.rb +9 -0
- data/lib/mtncloud/api/vdi_gateways_interface.rb +9 -0
- data/lib/mtncloud/api/vdi_interface.rb +28 -0
- data/lib/mtncloud/api/vdi_pools_interface.rb +19 -0
- data/lib/mtncloud/api/virtual_images_interface.rb +210 -0
- data/lib/mtncloud/api/virtual_servers_interface.rb +9 -0
- data/lib/mtncloud/api/virtual_switches_interface.rb +9 -0
- data/lib/mtncloud/api/whitelabel_settings_interface.rb +57 -0
- data/lib/mtncloud/api/whoami_interface.rb +13 -0
- data/lib/mtncloud/api/wiki_interface.rb +48 -0
- data/lib/mtncloud/api.rb +12 -0
- data/lib/mtncloud/benchmarking.rb +259 -0
- data/lib/mtncloud/cli/cli_command.rb +2281 -0
- data/lib/mtncloud/cli/cli_registry.rb +337 -0
- data/lib/mtncloud/cli/commands/access_token_command.rb +201 -0
- data/lib/mtncloud/cli/commands/account_groups_command.rb +530 -0
- data/lib/mtncloud/cli/commands/activity_command.rb +149 -0
- data/lib/mtncloud/cli/commands/affinity_groups_command.rb +554 -0
- data/lib/mtncloud/cli/commands/alias_command.rb +278 -0
- data/lib/mtncloud/cli/commands/appliance_settings_command.rb +382 -0
- data/lib/mtncloud/cli/commands/approvals_command.rb +236 -0
- data/lib/mtncloud/cli/commands/apps.rb +2549 -0
- data/lib/mtncloud/cli/commands/archives_command.rb +2161 -0
- data/lib/mtncloud/cli/commands/audit.rb +188 -0
- data/lib/mtncloud/cli/commands/backup_jobs_command.rb +366 -0
- data/lib/mtncloud/cli/commands/backup_restores_command.rb +144 -0
- data/lib/mtncloud/cli/commands/backup_results_command.rb +149 -0
- data/lib/mtncloud/cli/commands/backup_services_command.rb +44 -0
- data/lib/mtncloud/cli/commands/backup_settings_command.rb +184 -0
- data/lib/mtncloud/cli/commands/backup_types_command.rb +53 -0
- data/lib/mtncloud/cli/commands/backups_command.rb +674 -0
- data/lib/mtncloud/cli/commands/benchmark_command.rb +458 -0
- data/lib/mtncloud/cli/commands/blueprints_command.rb +2297 -0
- data/lib/mtncloud/cli/commands/boot_scripts_command.rb +379 -0
- data/lib/mtncloud/cli/commands/budgets_command.rb +754 -0
- data/lib/mtncloud/cli/commands/cat_command.rb +45 -0
- data/lib/mtncloud/cli/commands/catalog_item_types_command.rb +858 -0
- data/lib/mtncloud/cli/commands/certificates_command.rb +575 -0
- data/lib/mtncloud/cli/commands/change_password_command.rb +132 -0
- data/lib/mtncloud/cli/commands/clients_command.rb +331 -0
- data/lib/mtncloud/cli/commands/cloud_datastores_command.rb +543 -0
- data/lib/mtncloud/cli/commands/cloud_folders_command.rb +512 -0
- data/lib/mtncloud/cli/commands/cloud_resource_pools_command.rb +870 -0
- data/lib/mtncloud/cli/commands/clouds.rb +2037 -0
- data/lib/mtncloud/cli/commands/clouds_types.rb +140 -0
- data/lib/mtncloud/cli/commands/cluster_types.rb +179 -0
- data/lib/mtncloud/cli/commands/clusters.rb +5855 -0
- data/lib/mtncloud/cli/commands/coloring_command.rb +82 -0
- data/lib/mtncloud/cli/commands/containers_command.rb +967 -0
- data/lib/mtncloud/cli/commands/credential_types_command.rb +36 -0
- data/lib/mtncloud/cli/commands/credentials_command.rb +124 -0
- data/lib/mtncloud/cli/commands/curl_command.rb +168 -0
- data/lib/mtncloud/cli/commands/cypher_command.rb +587 -0
- data/lib/mtncloud/cli/commands/dashboard_command.rb +300 -0
- data/lib/mtncloud/cli/commands/debug_command.rb +75 -0
- data/lib/mtncloud/cli/commands/deploy.rb +485 -0
- data/lib/mtncloud/cli/commands/deployments.rb +923 -0
- data/lib/mtncloud/cli/commands/deploys.rb +485 -0
- data/lib/mtncloud/cli/commands/distributed_workers_command.rb +282 -0
- data/lib/mtncloud/cli/commands/echo_command.rb +61 -0
- data/lib/mtncloud/cli/commands/edit_profile_command.rb +57 -0
- data/lib/mtncloud/cli/commands/edit_rc_command.rb +77 -0
- data/lib/mtncloud/cli/commands/email_templates_command.rb +373 -0
- data/lib/mtncloud/cli/commands/environments_command.rb +401 -0
- data/lib/mtncloud/cli/commands/execute_schedules_command.rb +735 -0
- data/lib/mtncloud/cli/commands/execution_request_command.rb +333 -0
- data/lib/mtncloud/cli/commands/exit_command.rb +34 -0
- data/lib/mtncloud/cli/commands/file_copy_request_command.rb +352 -0
- data/lib/mtncloud/cli/commands/forgot_password.rb +133 -0
- data/lib/mtncloud/cli/commands/get_prompt_command.rb +36 -0
- data/lib/mtncloud/cli/commands/groups.rb +818 -0
- data/lib/mtncloud/cli/commands/guidance_command.rb +529 -0
- data/lib/mtncloud/cli/commands/guidance_settings.rb +148 -0
- data/lib/mtncloud/cli/commands/health_command.rb +1046 -0
- data/lib/mtncloud/cli/commands/history_command.rb +60 -0
- data/lib/mtncloud/cli/commands/host_vm_groups_command.rb +423 -0
- data/lib/mtncloud/cli/commands/hosts.rb +2986 -0
- data/lib/mtncloud/cli/commands/hub.rb +215 -0
- data/lib/mtncloud/cli/commands/image_builder_command.rb +1197 -0
- data/lib/mtncloud/cli/commands/instance_types.rb +163 -0
- data/lib/mtncloud/cli/commands/instances.rb +5813 -0
- data/lib/mtncloud/cli/commands/integrations_command.rb +1412 -0
- data/lib/mtncloud/cli/commands/invoices_command.rb +1151 -0
- data/lib/mtncloud/cli/commands/jobs_command.rb +998 -0
- data/lib/mtncloud/cli/commands/key_pairs.rb +470 -0
- data/lib/mtncloud/cli/commands/library_cluster_layouts_command.rb +901 -0
- data/lib/mtncloud/cli/commands/library_cluster_packages_command.rb +552 -0
- data/lib/mtncloud/cli/commands/library_container_scripts_command.rb +446 -0
- data/lib/mtncloud/cli/commands/library_container_templates_command.rb +526 -0
- data/lib/mtncloud/cli/commands/library_container_types_command.rb +698 -0
- data/lib/mtncloud/cli/commands/library_forms_command.rb +625 -0
- data/lib/mtncloud/cli/commands/library_instance_types_command.rb +668 -0
- data/lib/mtncloud/cli/commands/library_layouts_command.rb +849 -0
- data/lib/mtncloud/cli/commands/library_operating_systems_command.rb +671 -0
- data/lib/mtncloud/cli/commands/library_option_lists_command.rb +503 -0
- data/lib/mtncloud/cli/commands/library_option_types_command.rb +329 -0
- data/lib/mtncloud/cli/commands/library_spec_templates_command.rb +547 -0
- data/lib/mtncloud/cli/commands/library_upgrades_command.rb +596 -0
- data/lib/mtncloud/cli/commands/license.rb +475 -0
- data/lib/mtncloud/cli/commands/load_balancer_monitors.rb +70 -0
- data/lib/mtncloud/cli/commands/load_balancer_pool_nodes.rb +87 -0
- data/lib/mtncloud/cli/commands/load_balancer_pools.rb +129 -0
- data/lib/mtncloud/cli/commands/load_balancer_profiles.rb +64 -0
- data/lib/mtncloud/cli/commands/load_balancer_types.rb +38 -0
- data/lib/mtncloud/cli/commands/load_balancer_virtual_servers.rb +147 -0
- data/lib/mtncloud/cli/commands/load_balancers.rb +189 -0
- data/lib/mtncloud/cli/commands/log_level_command.rb +54 -0
- data/lib/mtncloud/cli/commands/log_settings_command.rb +443 -0
- data/lib/mtncloud/cli/commands/login.rb +148 -0
- data/lib/mtncloud/cli/commands/logout.rb +63 -0
- data/lib/mtncloud/cli/commands/logs_command.rb +140 -0
- data/lib/mtncloud/cli/commands/man_command.rb +409 -0
- data/lib/mtncloud/cli/commands/migrations.rb +453 -0
- data/lib/mtncloud/cli/commands/monitoring_alerts_command.rb +791 -0
- data/lib/mtncloud/cli/commands/monitoring_apps_command.rb +716 -0
- data/lib/mtncloud/cli/commands/monitoring_checks_command.rb +766 -0
- data/lib/mtncloud/cli/commands/monitoring_contacts_command.rb +339 -0
- data/lib/mtncloud/cli/commands/monitoring_groups_command.rb +674 -0
- data/lib/mtncloud/cli/commands/monitoring_incidents_command.rb +835 -0
- data/lib/mtncloud/cli/commands/monitoring_settings.rb +212 -0
- data/lib/mtncloud/cli/commands/network_dhcp_relays_command.rb +375 -0
- data/lib/mtncloud/cli/commands/network_dhcp_servers_command.rb +366 -0
- data/lib/mtncloud/cli/commands/network_domains_command.rb +896 -0
- data/lib/mtncloud/cli/commands/network_edge_clusters_command.rb +288 -0
- data/lib/mtncloud/cli/commands/network_firewalls_command.rb +783 -0
- data/lib/mtncloud/cli/commands/network_floating_ips.rb +147 -0
- data/lib/mtncloud/cli/commands/network_groups_command.rb +654 -0
- data/lib/mtncloud/cli/commands/network_pool_server_types.rb +20 -0
- data/lib/mtncloud/cli/commands/network_pool_servers_command.rb +318 -0
- data/lib/mtncloud/cli/commands/network_pools_command.rb +903 -0
- data/lib/mtncloud/cli/commands/network_proxies_command.rb +578 -0
- data/lib/mtncloud/cli/commands/network_routers_command.rb +2628 -0
- data/lib/mtncloud/cli/commands/network_security_server_types.rb +20 -0
- data/lib/mtncloud/cli/commands/network_server_groups_command.rb +222 -0
- data/lib/mtncloud/cli/commands/network_server_types_command.rb +20 -0
- data/lib/mtncloud/cli/commands/network_servers_command.rb +398 -0
- data/lib/mtncloud/cli/commands/network_services_command.rb +198 -0
- data/lib/mtncloud/cli/commands/network_static_routes_command.rb +451 -0
- data/lib/mtncloud/cli/commands/network_transport_zones_command.rb +412 -0
- data/lib/mtncloud/cli/commands/networks_command.rb +1692 -0
- data/lib/mtncloud/cli/commands/open_command.rb +30 -0
- data/lib/mtncloud/cli/commands/options.rb +231 -0
- data/lib/mtncloud/cli/commands/packages_command.rb +822 -0
- data/lib/mtncloud/cli/commands/ping.rb +242 -0
- data/lib/mtncloud/cli/commands/plugins.rb +214 -0
- data/lib/mtncloud/cli/commands/policies_command.rb +990 -0
- data/lib/mtncloud/cli/commands/power_schedules_command.rb +731 -0
- data/lib/mtncloud/cli/commands/preseed_scripts_command.rb +378 -0
- data/lib/mtncloud/cli/commands/price_sets_command.rb +557 -0
- data/lib/mtncloud/cli/commands/prices_command.rb +692 -0
- data/lib/mtncloud/cli/commands/processes_command.rb +469 -0
- data/lib/mtncloud/cli/commands/projects_command.rb +537 -0
- data/lib/mtncloud/cli/commands/provisioning_licenses_command.rb +509 -0
- data/lib/mtncloud/cli/commands/provisioning_settings_command.rb +238 -0
- data/lib/mtncloud/cli/commands/recent_activity_command.rb +171 -0
- data/lib/mtncloud/cli/commands/remote.rb +1818 -0
- data/lib/mtncloud/cli/commands/reports_command.rb +724 -0
- data/lib/mtncloud/cli/commands/resource_pool_groups_command.rb +586 -0
- data/lib/mtncloud/cli/commands/rm_command.rb +14 -0
- data/lib/mtncloud/cli/commands/roles.rb +3120 -0
- data/lib/mtncloud/cli/commands/scale_thresholds.rb +103 -0
- data/lib/mtncloud/cli/commands/search_command.rb +184 -0
- data/lib/mtncloud/cli/commands/security_group_rules.rb +230 -0
- data/lib/mtncloud/cli/commands/security_groups.rb +1294 -0
- data/lib/mtncloud/cli/commands/security_package_types.rb +32 -0
- data/lib/mtncloud/cli/commands/security_packages.rb +84 -0
- data/lib/mtncloud/cli/commands/security_scans.rb +107 -0
- data/lib/mtncloud/cli/commands/self_service_command.rb +17 -0
- data/lib/mtncloud/cli/commands/server_devices_command.rb +207 -0
- data/lib/mtncloud/cli/commands/service_catalog_command.rb +1528 -0
- data/lib/mtncloud/cli/commands/service_plans_command.rb +895 -0
- data/lib/mtncloud/cli/commands/set_prompt_command.rb +51 -0
- data/lib/mtncloud/cli/commands/setup.rb +423 -0
- data/lib/mtncloud/cli/commands/shell.rb +717 -0
- data/lib/mtncloud/cli/commands/sleep_command.rb +43 -0
- data/lib/mtncloud/cli/commands/snapshots.rb +156 -0
- data/lib/mtncloud/cli/commands/source_command.rb +30 -0
- data/lib/mtncloud/cli/commands/ssl_verification_command.rb +47 -0
- data/lib/mtncloud/cli/commands/storage_datastores.rb +457 -0
- data/lib/mtncloud/cli/commands/storage_providers_command.rb +1401 -0
- data/lib/mtncloud/cli/commands/storage_server_types.rb +45 -0
- data/lib/mtncloud/cli/commands/storage_servers.rb +116 -0
- data/lib/mtncloud/cli/commands/storage_volume_types.rb +45 -0
- data/lib/mtncloud/cli/commands/storage_volumes.rb +236 -0
- data/lib/mtncloud/cli/commands/subnets_command.rb +819 -0
- data/lib/mtncloud/cli/commands/support_bundles_command.rb +606 -0
- data/lib/mtncloud/cli/commands/systems.rb +1203 -0
- data/lib/mtncloud/cli/commands/tasks.rb +1391 -0
- data/lib/mtncloud/cli/commands/tee_command.rb +14 -0
- data/lib/mtncloud/cli/commands/tenants_command.rb +315 -0
- data/lib/mtncloud/cli/commands/tokens_command.rb +391 -0
- data/lib/mtncloud/cli/commands/update_command.rb +46 -0
- data/lib/mtncloud/cli/commands/usage_command.rb +203 -0
- data/lib/mtncloud/cli/commands/user_groups_command.rb +512 -0
- data/lib/mtncloud/cli/commands/user_settings_command.rb +849 -0
- data/lib/mtncloud/cli/commands/user_sources_command.rb +1065 -0
- data/lib/mtncloud/cli/commands/users.rb +897 -0
- data/lib/mtncloud/cli/commands/vdi_allocations_command.rb +159 -0
- data/lib/mtncloud/cli/commands/vdi_apps_command.rb +317 -0
- data/lib/mtncloud/cli/commands/vdi_command.rb +359 -0
- data/lib/mtncloud/cli/commands/vdi_gateways_command.rb +290 -0
- data/lib/mtncloud/cli/commands/vdi_pools_command.rb +574 -0
- data/lib/mtncloud/cli/commands/version_command.rb +38 -0
- data/lib/mtncloud/cli/commands/view.rb +107 -0
- data/lib/mtncloud/cli/commands/virtual_images.rb +1362 -0
- data/lib/mtncloud/cli/commands/virtual_switches_command.rb +336 -0
- data/lib/mtncloud/cli/commands/whitelabel_settings_command.rb +653 -0
- data/lib/mtncloud/cli/commands/whoami.rb +323 -0
- data/lib/mtncloud/cli/commands/wiki_command.rb +448 -0
- data/lib/mtncloud/cli/commands/workflows.rb +1022 -0
- data/lib/mtncloud/cli/credentials.rb +482 -0
- data/lib/mtncloud/cli/dot_file.rb +180 -0
- data/lib/mtncloud/cli/error_handler.rb +307 -0
- data/lib/mtncloud/cli/errors.rb +53 -0
- data/lib/mtncloud/cli/expression_parser.rb +153 -0
- data/lib/mtncloud/cli/mixins/accounts_helper.rb +484 -0
- data/lib/mtncloud/cli/mixins/affinity_helper.rb +118 -0
- data/lib/mtncloud/cli/mixins/backups_helper.rb +200 -0
- data/lib/mtncloud/cli/mixins/deployments_helper.rb +163 -0
- data/lib/mtncloud/cli/mixins/execution_request_helper.rb +50 -0
- data/lib/mtncloud/cli/mixins/infrastructure_helper.rb +966 -0
- data/lib/mtncloud/cli/mixins/jobs_helper.rb +202 -0
- data/lib/mtncloud/cli/mixins/library_helper.rb +539 -0
- data/lib/mtncloud/cli/mixins/load_balancers_helper.rb +176 -0
- data/lib/mtncloud/cli/mixins/logs_helper.rb +131 -0
- data/lib/mtncloud/cli/mixins/monitoring_helper.rb +876 -0
- data/lib/mtncloud/cli/mixins/networks_helper.rb +62 -0
- data/lib/mtncloud/cli/mixins/operations_helper.rb +41 -0
- data/lib/mtncloud/cli/mixins/option_source_helper.rb +254 -0
- data/lib/mtncloud/cli/mixins/print_helper.rb +1631 -0
- data/lib/mtncloud/cli/mixins/processes_helper.rb +286 -0
- data/lib/mtncloud/cli/mixins/prompt_helper.rb +136 -0
- data/lib/mtncloud/cli/mixins/provisioning_helper.rb +2963 -0
- data/lib/mtncloud/cli/mixins/remote_helper.rb +139 -0
- data/lib/mtncloud/cli/mixins/rest_command.rb +994 -0
- data/lib/mtncloud/cli/mixins/secondary_rest_command.rb +700 -0
- data/lib/mtncloud/cli/mixins/storage_servers_helper.rb +93 -0
- data/lib/mtncloud/cli/mixins/storage_volumes_helper.rb +76 -0
- data/lib/mtncloud/cli/mixins/vdi_helper.rb +246 -0
- data/lib/mtncloud/cli/mixins/whoami_helper.rb +73 -0
- data/lib/mtncloud/cli/option_parser.rb +127 -0
- data/lib/mtncloud/cli/option_types.rb +1622 -0
- data/lib/mtncloud/cli/version.rb +6 -0
- data/lib/mtncloud/cli.rb +105 -0
- data/lib/mtncloud/currency.rb +184 -0
- data/lib/mtncloud/ext/hash.rb +109 -0
- data/lib/mtncloud/ext/nil_class.rb +5 -0
- data/lib/mtncloud/ext/rest_client.rb +37 -0
- data/lib/mtncloud/ext/string.rb +66 -0
- data/lib/mtncloud/formatters.rb +511 -0
- data/lib/mtncloud/logging.rb +185 -0
- data/lib/mtncloud/morpkg.rb +119 -0
- data/lib/mtncloud/rest_client.rb +104 -0
- data/lib/mtncloud/routes.rb +304 -0
- data/lib/mtncloud/terminal.rb +528 -0
- data/lib/mtncloud/util.rb +29 -0
- data/lib/mtncloud.rb +5 -0
- data/mtncloud-cli.gemspec +38 -0
- data/test/api/affinity_groups_interface_test.rb +43 -0
- data/test/api/clients_interface_test.rb +23 -0
- data/test/api/clouds_interface_test.rb +67 -0
- data/test/api/containers_interface_test.rb +68 -0
- data/test/api/host_vm_groups_interface_test.rb +35 -0
- data/test/api/instances_interface_test.rb +22 -0
- data/test/api/network_routers_interface_test.rb +22 -0
- data/test/api/systems_interface_test.rb +26 -0
- data/test/api/whoami_interface_test.rb +14 -0
- data/test/branding_test.rb +160 -0
- data/test/cli/access_token_test.rb +36 -0
- data/test/cli/affinity_groups_cloud_cluster_scope_test.rb +270 -0
- data/test/cli/affinity_groups_cluster_scope_test.rb +171 -0
- data/test/cli/affinity_groups_test.rb +57 -0
- data/test/cli/affinity_groups_violations_scope_test.rb +141 -0
- data/test/cli/affinity_helper_test.rb +367 -0
- data/test/cli/auth_test.rb +88 -0
- data/test/cli/backups_test.rb +212 -0
- data/test/cli/cli_test.rb +48 -0
- data/test/cli/clients_test.rb +45 -0
- data/test/cli/clusters_uuid_test.rb +146 -0
- data/test/cli/containers_test.rb +92 -0
- data/test/cli/distributed_workers_test.rb +68 -0
- data/test/cli/help_test.rb +25 -0
- data/test/cli/host_vm_groups_cluster_scope_test.rb +187 -0
- data/test/cli/host_vm_groups_test.rb +63 -0
- data/test/cli/infrastructure_helper_cluster_resolver_test.rb +250 -0
- data/test/cli/instances_test.rb +37 -0
- data/test/cli/man_test.rb +14 -0
- data/test/cli/migrations_test.rb +39 -0
- data/test/cli/network_routers_test.rb +65 -0
- data/test/cli/remote_test.rb +89 -0
- data/test/cli/roles_test.rb +47 -0
- data/test/cli/shell_test.rb +81 -0
- data/test/cli/storage_volumes_test.rb +79 -0
- data/test/cli/systems_test.rb +206 -0
- data/test/cli/version_test.rb +23 -0
- data/test/cli/view_test.rb +55 -0
- data/test/cli/whoami_test.rb +17 -0
- data/test/mtncloud_test.rb +16 -0
- data/test/test_case.rb +342 -0
- data/test/test_config.rb +137 -0
- data/test/test_data_helper.rb +97 -0
- metadata +807 -0
|
@@ -0,0 +1,2281 @@
|
|
|
1
|
+
require 'mtncloud/logging'
|
|
2
|
+
require 'mtncloud/benchmarking'
|
|
3
|
+
require 'mtncloud/terminal'
|
|
4
|
+
require 'mtncloud/cli/cli_registry'
|
|
5
|
+
require 'mtncloud/cli/option_parser'
|
|
6
|
+
require 'mtncloud/cli/option_types'
|
|
7
|
+
require 'mtncloud/cli/mixins/print_helper'
|
|
8
|
+
require 'mtncloud/cli/credentials'
|
|
9
|
+
#require 'mtncloud/cli/commands/shell'
|
|
10
|
+
#require 'mtncloud/cli/commands/remote'
|
|
11
|
+
require 'mtncloud/api/api_client'
|
|
12
|
+
|
|
13
|
+
module MtnCloud
|
|
14
|
+
module Cli
|
|
15
|
+
# Module to be included by every CLI command so that commands get registered
|
|
16
|
+
# This mixin defines a print and puts method, and delegates
|
|
17
|
+
# todo: use delegate
|
|
18
|
+
module CliCommand
|
|
19
|
+
|
|
20
|
+
def self.included(base)
|
|
21
|
+
base.send :include, MtnCloud::Cli::PrintHelper
|
|
22
|
+
base.send :include, MtnCloud::Cli::PromptHelper
|
|
23
|
+
base.send :include, MtnCloud::Benchmarking::HasBenchmarking
|
|
24
|
+
base.extend ClassMethods
|
|
25
|
+
MtnCloud::Cli::CliRegistry.add(base, base.command_name)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# the beginning of instance variables from optparse !
|
|
29
|
+
|
|
30
|
+
# this setting makes it easy for the called to disable prompting
|
|
31
|
+
attr_reader :no_prompt
|
|
32
|
+
|
|
33
|
+
# @return [MtnCloud::Terminal] the terminal this command is being executed inside of
|
|
34
|
+
def my_terminal
|
|
35
|
+
@my_terminal ||= MtnCloud::Terminal.instance
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# set the terminal running this command.
|
|
39
|
+
# @param term [MtnCloudTerminal] the terminal this command is assigned to
|
|
40
|
+
# @return the Terminal this command is being executed inside of
|
|
41
|
+
def my_terminal=(term)
|
|
42
|
+
if !term.is_a?(MtnCloud::Terminal)
|
|
43
|
+
raise "CliCommand (#{self.class}) my_terminal= expects object of type Terminal and instead got a #{term.class}"
|
|
44
|
+
end
|
|
45
|
+
@my_terminal = term
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# delegate :print, to: :my_terminal
|
|
50
|
+
# delegate :puts, to: :my_terminal
|
|
51
|
+
# or . . . bum bum bummm
|
|
52
|
+
# a paradigm shift away from include and use module functions instead.
|
|
53
|
+
# module_function :print, puts
|
|
54
|
+
# delegate :puts, to: :my_terminal
|
|
55
|
+
|
|
56
|
+
def print(*msgs)
|
|
57
|
+
my_terminal.stdout.print(*msgs)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def println(*msgs)
|
|
61
|
+
print(*msgs)
|
|
62
|
+
print "\n"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def puts(*msgs)
|
|
66
|
+
my_terminal.stdout.puts(*msgs)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def print_error(*msgs)
|
|
70
|
+
my_terminal.stderr.print(*msgs)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def puts_error(*msgs)
|
|
74
|
+
my_terminal.stderr.puts(*msgs)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# todo: customizable output color, other than cyan.
|
|
78
|
+
# def terminal_fg
|
|
79
|
+
# end
|
|
80
|
+
# def cyan
|
|
81
|
+
# Term::ANSIColor.black
|
|
82
|
+
# end
|
|
83
|
+
|
|
84
|
+
# todo: use terminal.stdin
|
|
85
|
+
# def readline(*msgs)
|
|
86
|
+
# @my_terminal.stdin.readline(*msgs)
|
|
87
|
+
# end
|
|
88
|
+
|
|
89
|
+
# todo: maybe...
|
|
90
|
+
# disabled prompting for this command
|
|
91
|
+
# def noninteractive()
|
|
92
|
+
# @no_prompt = true
|
|
93
|
+
# self
|
|
94
|
+
# end
|
|
95
|
+
|
|
96
|
+
# whether to prompt or not, this is true by default.
|
|
97
|
+
def interactive?
|
|
98
|
+
@no_prompt != true
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def raise_command_error(msg, args=[], optparse=nil, exit_code=nil)
|
|
102
|
+
raise MtnCloud::Cli::CommandError.new(msg, args, optparse, exit_code)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def raise_args_error(msg, args=[], optparse=nil, exit_code=nil)
|
|
106
|
+
raise MtnCloud::Cli::CommandArgumentsError.new(msg, args, optparse, exit_code)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# parse_id_list splits returns the given id_list with its values split on a comma
|
|
110
|
+
# your id values cannot contain a comma, atm...
|
|
111
|
+
# @param id_list [String or Array of Strings]
|
|
112
|
+
# @param delim [String] Default is a comma and any surrounding white space.
|
|
113
|
+
# @return array of values
|
|
114
|
+
def parse_id_list(id_list, delim=/\s*\,\s*/)
|
|
115
|
+
[id_list].flatten.collect {|it| it ? it.to_s.split(delim) : nil }.flatten.compact
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def parse_bytes_param(bytes_param, option, assumed_unit = nil, allow_zero = false)
|
|
119
|
+
if bytes_param && ( bytes_param.to_f > 0 || ( allow_zero && bytes_param.to_i == 0 ))
|
|
120
|
+
bytes_param.upcase!
|
|
121
|
+
multiplier = 1
|
|
122
|
+
unit = nil
|
|
123
|
+
number = (bytes_param.to_f == bytes_param.to_i ? bytes_param.to_i : bytes_param.to_f)
|
|
124
|
+
if (bytes_param.end_with? 'GB') || ((!bytes_param.end_with? 'MB') && assumed_unit == 'GB')
|
|
125
|
+
unit = 'GB'
|
|
126
|
+
multiplier = 1024 * 1024 * 1024
|
|
127
|
+
elsif (bytes_param.end_with? 'MB') || assumed_unit == 'MB'
|
|
128
|
+
unit = 'MB'
|
|
129
|
+
multiplier = 1024 * 1024
|
|
130
|
+
end
|
|
131
|
+
return {:bytes_param => bytes_param, :bytes => number * multiplier, :number => number, :multiplier => multiplier, :unit => unit}
|
|
132
|
+
end
|
|
133
|
+
raise_command_error "Invalid value for #{option} option"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# this returns all the options passed in by -O, parsed all nicely into objects.
|
|
137
|
+
def parse_passed_options(options, parse_opts={})
|
|
138
|
+
excludes = [parse_opts[:exclude], parse_opts[:excludes]].flatten.compact
|
|
139
|
+
passed_options = options[:options] ? options[:options].reject {|k,v| k.is_a?(Symbol) || excludes.include?(k) } : {}
|
|
140
|
+
return passed_options
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Appends Array of OptionType definitions to an OptionParser instance
|
|
144
|
+
# This adds an option like --fieldContext.fieldName="VALUE"
|
|
145
|
+
# @param opts [OptionParser]
|
|
146
|
+
# @param options [Hash] output map that is being constructed
|
|
147
|
+
# @param option_types [Array] list of OptionType definitions to add
|
|
148
|
+
# @return void, this modifies the opts in place.
|
|
149
|
+
def build_option_type_options(opts, options, option_types=[])
|
|
150
|
+
#opts.separator ""
|
|
151
|
+
#opts.separator "Options:"
|
|
152
|
+
options[:options] ||= {} # this is where these go..for now
|
|
153
|
+
options[:option_types] = (options[:option_types] || []) + option_types
|
|
154
|
+
custom_options = options[:options]
|
|
155
|
+
|
|
156
|
+
# add each one to the OptionParser
|
|
157
|
+
option_types.each do |option_type|
|
|
158
|
+
# skip hidden types
|
|
159
|
+
if option_type['type'] == 'hidden'
|
|
160
|
+
next
|
|
161
|
+
end
|
|
162
|
+
if option_type['fieldName'].empty?
|
|
163
|
+
puts_error "Missing fieldName for option type: #{option_type}" if MtnCloud::Logging.debug?
|
|
164
|
+
next
|
|
165
|
+
end
|
|
166
|
+
full_field_name = option_type['fieldContext'].to_s.empty? ? option_type['fieldName'] : "#{option_type['fieldContext']}.#{option_type['fieldName']}"
|
|
167
|
+
field_namespace = full_field_name.split(".")
|
|
168
|
+
field_name = field_namespace.pop
|
|
169
|
+
|
|
170
|
+
description = "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}"
|
|
171
|
+
if option_type['description']
|
|
172
|
+
# description << "\n #{option_type['description']}"
|
|
173
|
+
description << " - #{option_type['description']}"
|
|
174
|
+
end
|
|
175
|
+
if option_type['defaultValue']
|
|
176
|
+
description << ". Default: #{option_type['defaultValue']}"
|
|
177
|
+
end
|
|
178
|
+
if option_type['helpBlock']
|
|
179
|
+
description << "\n #{option_type['helpBlock']}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# description = option_type['description'].to_s
|
|
183
|
+
# if option_type['defaultValue']
|
|
184
|
+
# description = "#{description} Default: #{option_type['defaultValue']}"
|
|
185
|
+
# end
|
|
186
|
+
# if option_type['required'] == true
|
|
187
|
+
# description = "(Required) #{description}"
|
|
188
|
+
# end
|
|
189
|
+
|
|
190
|
+
value_label = "VALUE"
|
|
191
|
+
if option_type['placeHolder']
|
|
192
|
+
value_label = option_type['placeHolder']
|
|
193
|
+
elsif option_type['type'] == 'checkbox'
|
|
194
|
+
value_label = '[on|off]' # or.. true|false
|
|
195
|
+
elsif option_type['type'] == 'number'
|
|
196
|
+
value_label = 'NUMBER'
|
|
197
|
+
elsif option_type['type'] == 'multiSelect'
|
|
198
|
+
value_label = 'LIST'
|
|
199
|
+
# elsif option_type['type'] == 'select'
|
|
200
|
+
# value_label = 'SELECT'
|
|
201
|
+
# elsif option['type'] == 'select'
|
|
202
|
+
end
|
|
203
|
+
if option_type['optionalValue']
|
|
204
|
+
value_label = "[#{value_label}]"
|
|
205
|
+
end
|
|
206
|
+
full_option = "--#{full_field_name} #{value_label}"
|
|
207
|
+
# switch is an alias for the full option name, fieldName is the default
|
|
208
|
+
if option_type['switch']
|
|
209
|
+
full_option = "--#{option_type['switch']} #{value_label}"
|
|
210
|
+
end
|
|
211
|
+
arg1, arg2 = full_option, String
|
|
212
|
+
if option_type['shorthand']
|
|
213
|
+
arg1, arg2 = full_option, option_type['shorthand']
|
|
214
|
+
end
|
|
215
|
+
opts.on(arg1, arg2, description) do |val|
|
|
216
|
+
if option_type['type'] == 'checkbox'
|
|
217
|
+
val = (val.to_s != 'false' && val.to_s != 'off')
|
|
218
|
+
elsif option_type['dataType'] != 'string'
|
|
219
|
+
# 'dataType': 'string' added to cli to avoid auto conversion to JSON
|
|
220
|
+
# attempt to parse JSON, this allows blank arrays for multiSelect like --tenants []
|
|
221
|
+
if (val.to_s[0] == '{' && val.to_s[-1] == '}') || (val.to_s[0] == '[' && val.to_s[-1] == ']')
|
|
222
|
+
begin
|
|
223
|
+
val = JSON.parse(val)
|
|
224
|
+
rescue
|
|
225
|
+
MtnCloud::Logging::DarkPrinter.puts "Failed to parse option value '#{val}' as JSON" if MtnCloud::Logging.debug?
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
cur_namespace = custom_options
|
|
230
|
+
field_namespace.each do |ns|
|
|
231
|
+
next if ns.empty?
|
|
232
|
+
cur_namespace[ns.to_s] ||= {}
|
|
233
|
+
cur_namespace = cur_namespace[ns.to_s]
|
|
234
|
+
end
|
|
235
|
+
cur_namespace[field_name] = val
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# todo: all the various types
|
|
239
|
+
# number
|
|
240
|
+
# checkbox [on|off]
|
|
241
|
+
# select for optionSource and selectOptions
|
|
242
|
+
|
|
243
|
+
end
|
|
244
|
+
opts
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
## the standard options for a command that makes api requests (most of them)
|
|
248
|
+
|
|
249
|
+
def build_standard_get_options(opts, options, includes=[], excludes=[])
|
|
250
|
+
build_common_options(opts, options, includes + [:query, :json, :yaml, :csv, :fields, :select, :delim, :quiet, :dry_run, :remote], excludes)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def build_standard_post_options(opts, options, includes=[], excludes=[])
|
|
254
|
+
build_common_options(opts, options, includes + [:options, :payload, :json, :quiet, :dry_run, :remote], excludes)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def build_standard_put_options(opts, options, includes=[], excludes=[])
|
|
258
|
+
build_standard_post_options(opts, options, includes, excludes)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def build_standard_delete_options(opts, options, includes=[], excludes=[])
|
|
262
|
+
build_common_options(opts, options, includes + [:auto_confirm, :query, :json, :quiet, :dry_run, :remote], excludes)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# list is GET that supports phrase,max,offset,sort,direction
|
|
266
|
+
def build_standard_list_options(opts, options, includes=[], excludes=[])
|
|
267
|
+
build_standard_get_options(opts, options, [:list] + includes, excludes)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def build_standard_add_options(opts, options, includes=[], excludes=[])
|
|
271
|
+
build_standard_post_options(opts, options, includes, excludes)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def build_standard_add_many_options(opts, options, includes=[], excludes=[])
|
|
275
|
+
build_standard_post_options(opts, options, includes + [:payloads], excludes)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def build_standard_update_options(opts, options, includes=[], excludes=[])
|
|
279
|
+
build_standard_put_options(opts, options, includes, excludes)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def build_standard_remove_options(opts, options, includes=[], excludes=[])
|
|
283
|
+
build_standard_delete_options(opts, options, includes, excludes)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def build_standard_api_options(opts, options, includes=[], excludes=[])
|
|
287
|
+
build_common_options(opts, options, includes + [:query, :options, :payload, :json, :yaml, :csv, :fields, :select, :delim, :quiet, :dry_run, :remote], excludes)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# number of decimal places to show with curreny
|
|
291
|
+
def default_sigdig
|
|
292
|
+
2
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# appends to the passed OptionParser all the generic options
|
|
296
|
+
# @param opts [OptionParser] the option parser object being constructed
|
|
297
|
+
# @param options [Hash] the output Hash that is to being modified
|
|
298
|
+
# @param includes [Array] which options to include eg. :options, :json, :remote
|
|
299
|
+
# @return opts
|
|
300
|
+
def build_common_options(opts, options, includes=[], excludes=[])
|
|
301
|
+
#opts.separator ""
|
|
302
|
+
# opts.separator "Common options:"
|
|
303
|
+
option_keys = includes.clone
|
|
304
|
+
all_option_keys = option_keys.dup
|
|
305
|
+
# todo: support --quiet everywhere
|
|
306
|
+
# turn on some options all the time..
|
|
307
|
+
# unless command_name == "shell"
|
|
308
|
+
# option_keys << :quiet unless option_keys.include?(:quiet)
|
|
309
|
+
# end
|
|
310
|
+
|
|
311
|
+
# ensure commands can always access options[:options], until we can deprecate it...
|
|
312
|
+
options[:options] ||= {}
|
|
313
|
+
|
|
314
|
+
while (option_key = option_keys.shift) do
|
|
315
|
+
case option_key.to_sym
|
|
316
|
+
|
|
317
|
+
when :tenant, :account
|
|
318
|
+
# todo: let's deprecate this in favor of :tenant --tenant to keep -a reserved for --all perhaps?
|
|
319
|
+
opts.on('--tenant TENANT', String, "Tenant (Account) Name or ID") do |val|
|
|
320
|
+
options[:account] = val
|
|
321
|
+
end
|
|
322
|
+
opts.on('--tenant-id ID', String, "Tenant (Account) ID") do |val|
|
|
323
|
+
options[:account_id] = val
|
|
324
|
+
end
|
|
325
|
+
# todo: let's deprecate this in favor of :tenant --tenant to keep -a reserved for --all perhaps?
|
|
326
|
+
opts.on('-a','--account ACCOUNT', "Alias for --tenant") do |val|
|
|
327
|
+
options[:account] = val
|
|
328
|
+
end
|
|
329
|
+
opts.on('-A','--account-id ID', "Tenant (Account) ID") do |val|
|
|
330
|
+
options[:account_id] = val
|
|
331
|
+
end
|
|
332
|
+
opts.add_hidden_option('--tenant-id') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
333
|
+
opts.add_hidden_option('-a, --account') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
334
|
+
opts.add_hidden_option('-A, --account-id') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
335
|
+
|
|
336
|
+
when :details
|
|
337
|
+
opts.on('-a', '--all', "Show all details." ) do
|
|
338
|
+
options[:details] = true
|
|
339
|
+
end
|
|
340
|
+
opts.on('--details', '--details', "Show more details" ) do
|
|
341
|
+
options[:details] = true
|
|
342
|
+
end
|
|
343
|
+
opts.add_hidden_option('--details')
|
|
344
|
+
|
|
345
|
+
when :sigdig
|
|
346
|
+
opts.on('--sigdig DIGITS', "Significant digits to display for prices (currency). Default is #{default_sigdig}.") do |val|
|
|
347
|
+
options[:sigdig] = val.to_i
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
when :options
|
|
351
|
+
options[:options] ||= {}
|
|
352
|
+
opts.on( '-O', '--option OPTION', "Option in the format -O field=\"value\"" ) do |option|
|
|
353
|
+
# todo: look ahead and parse ALL the option=value args after -O switch
|
|
354
|
+
#custom_option_args = option.split('=')
|
|
355
|
+
custom_option_args = option.sub(/\s?\=\s?/, '__OPTION_DELIM__').split('__OPTION_DELIM__')
|
|
356
|
+
custom_options = options[:options]
|
|
357
|
+
option_name_args = custom_option_args[0].split('.')
|
|
358
|
+
option_type = (options[:option_types] || []).find {|it| it['fieldName'] == custom_option_args[0]} || {}
|
|
359
|
+
if option_name_args.count > 1
|
|
360
|
+
nested_options = custom_options
|
|
361
|
+
option_name_args.each_with_index do |name_element,index|
|
|
362
|
+
if index < option_name_args.count - 1
|
|
363
|
+
nested_options[name_element] = nested_options[name_element] || {}
|
|
364
|
+
nested_options = nested_options[name_element]
|
|
365
|
+
else
|
|
366
|
+
val = custom_option_args[1]
|
|
367
|
+
unless option_type['noParse']
|
|
368
|
+
if (val.to_s[0] == '{' && val.to_s[-1] == '}') || (val.to_s[0] == '[' && val.to_s[-1] == ']')
|
|
369
|
+
begin
|
|
370
|
+
val = JSON.parse(val)
|
|
371
|
+
rescue
|
|
372
|
+
MtnCloud::Logging::DarkPrinter.puts "Failed to parse option value '#{val}' as JSON" if MtnCloud::Logging.debug?
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
nested_options[name_element] = val
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
else
|
|
380
|
+
val = custom_option_args[1]
|
|
381
|
+
unless option_type['noParse']
|
|
382
|
+
if (val.to_s[0] == '{' && val.to_s[-1] == '}') || (val.to_s[0] == '[' && val.to_s[-1] == ']')
|
|
383
|
+
begin
|
|
384
|
+
val = JSON.parse(val)
|
|
385
|
+
rescue
|
|
386
|
+
MtnCloud::Logging::DarkPrinter.puts "Failed to parse option value '#{val}' as JSON" if MtnCloud::Logging.debug?
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
custom_options[custom_option_args[0]] = val
|
|
391
|
+
end
|
|
392
|
+
# convert "true","on" and "false","off" to true and false
|
|
393
|
+
unless options[:skip_booleanize]
|
|
394
|
+
custom_options.booleanize!
|
|
395
|
+
end
|
|
396
|
+
options[:options] = custom_options
|
|
397
|
+
end
|
|
398
|
+
# --always-prompt can be used with for update commands where it normally defaults to --no-prompt
|
|
399
|
+
opts.on('--prompt', "Always prompt for input on every option, even those not prompted for by default.") do
|
|
400
|
+
options[:always_prompt] = true
|
|
401
|
+
options[:options][:always_prompt] = true
|
|
402
|
+
end
|
|
403
|
+
opts.on('-N','--no-prompt', "No prompt, skips all input prompting.") do |val|
|
|
404
|
+
options[:no_prompt] = true
|
|
405
|
+
options[:options][:no_prompt] = true
|
|
406
|
+
end
|
|
407
|
+
# opts.on('--skip-prompt x,y,z', String, "Skip prompt, do not prompt for input of the specified options.") do |val|
|
|
408
|
+
# options[:skip_prompt] ||= []
|
|
409
|
+
# options[:skip_prompt] += parse_array(val)
|
|
410
|
+
# options[:options][:skip_prompt] = options[:skip_prompt]
|
|
411
|
+
# end
|
|
412
|
+
# opts.on('--only-prompt x,y,z', String, "Only prompt for input on the specified options.") do |val|
|
|
413
|
+
# options[:only_prompt] ||= []
|
|
414
|
+
# options[:only_prompt] += parse_array(val)
|
|
415
|
+
# options[:options][:only_prompt] = options[:only_prompt]
|
|
416
|
+
# end
|
|
417
|
+
opts.on('--no-options', String, "No options, skips all option parsing so no options are required and no default values are used.") do
|
|
418
|
+
options[:no_options] = true
|
|
419
|
+
options[:options][:no_options] = options[:no_options]
|
|
420
|
+
end
|
|
421
|
+
opts.on('--skip-options x,y,z', String, "Skip parsing of the specified options so that they are not required and their default value is not used.") do |val|
|
|
422
|
+
options[:skip_options] ||= []
|
|
423
|
+
options[:skip_options] += parse_array(val)
|
|
424
|
+
options[:options][:skip_options] = options[:skip_options]
|
|
425
|
+
end
|
|
426
|
+
# opts.on('--only-options x,y,z', String, "Only parse the specified options and skip all others.") do |val|
|
|
427
|
+
# options[:only_options] ||= []
|
|
428
|
+
# options[:only_options] += parse_array(val)
|
|
429
|
+
# options[:options][:only_options] = options[:only_options]
|
|
430
|
+
# end
|
|
431
|
+
|
|
432
|
+
# hide these while incubating
|
|
433
|
+
opts.add_hidden_option('--skip-prompt')
|
|
434
|
+
opts.add_hidden_option('--only-prompt')
|
|
435
|
+
opts.add_hidden_option('--no-options')
|
|
436
|
+
opts.add_hidden_option('--skip-options')
|
|
437
|
+
opts.add_hidden_option('--only-options')
|
|
438
|
+
|
|
439
|
+
when :payload
|
|
440
|
+
opts.on('--payload FILE', String, "Payload from a local JSON or YAML file, skip all prompting") do |val|
|
|
441
|
+
options[:payload_file] = val.to_s
|
|
442
|
+
begin
|
|
443
|
+
payload_file = File.expand_path(options[:payload_file])
|
|
444
|
+
if !File.exist?(payload_file) || !File.file?(payload_file)
|
|
445
|
+
raise ::OptionParser::InvalidOption.new("File not found: #{payload_file}")
|
|
446
|
+
#return false
|
|
447
|
+
end
|
|
448
|
+
if payload_file =~ /\.ya?ml\Z/
|
|
449
|
+
options[:payload] = YAML.load_file(payload_file)
|
|
450
|
+
else
|
|
451
|
+
options[:payload] = JSON.parse(File.read(payload_file))
|
|
452
|
+
end
|
|
453
|
+
rescue => ex
|
|
454
|
+
raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
opts.on('--payload-dir DIRECTORY', String, "Payload from a local directory containing 1-N JSON or YAML files, skip all prompting. This makes one request, merging all the files into a single payload.") do |val|
|
|
458
|
+
print_error yellow,"[DEPRECATED] The option `--payload-dir` is deprecated and will be removed. Use `--payloads` to make requests for each file in a directory.",reset,"\n"
|
|
459
|
+
options[:payload_dir] = val.to_s
|
|
460
|
+
payload_dir = File.expand_path(options[:payload_dir])
|
|
461
|
+
if !Dir.exist?(payload_dir) || !File.directory?(payload_dir)
|
|
462
|
+
raise ::OptionParser::InvalidOption.new("Directory not found: #{payload_dir}")
|
|
463
|
+
end
|
|
464
|
+
payload = {}
|
|
465
|
+
begin
|
|
466
|
+
merged_payload = {}
|
|
467
|
+
payload_files = []
|
|
468
|
+
payload_files += Dir["#{payload_dir}/*.json"]
|
|
469
|
+
payload_files += Dir["#{payload_dir}/*.yml"]
|
|
470
|
+
payload_files += Dir["#{payload_dir}/*.yaml"]
|
|
471
|
+
if payload_files.empty?
|
|
472
|
+
raise ::OptionParser::InvalidOption.new("No .json/yaml files found in config directory: #{payload_dir}")
|
|
473
|
+
end
|
|
474
|
+
payload_files.each do |payload_file|
|
|
475
|
+
MtnCloud::Logging::DarkPrinter.puts "parsing payload file: #{payload_file}" if MtnCloud::Logging.debug?
|
|
476
|
+
config_payload = {}
|
|
477
|
+
if payload_file =~ /\.ya?ml\Z/
|
|
478
|
+
config_payload = YAML.load_file(payload_file)
|
|
479
|
+
else
|
|
480
|
+
config_payload = JSON.parse(File.read(payload_file))
|
|
481
|
+
end
|
|
482
|
+
merged_payload.deep_merge!(config_payload)
|
|
483
|
+
end
|
|
484
|
+
options[:payload] = merged_payload
|
|
485
|
+
rescue => ex
|
|
486
|
+
raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
opts.add_hidden_option('--payload-dir')
|
|
490
|
+
opts.on('--payload-json JSON', String, "Payload JSON, skip all prompting") do |val|
|
|
491
|
+
begin
|
|
492
|
+
options[:payload] = JSON.parse(val.to_s)
|
|
493
|
+
rescue => ex
|
|
494
|
+
raise ::OptionParser::InvalidOption.new("Failed to parse payload as JSON. Error: #{ex.message}")
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
opts.on('--payload-yaml YAML', String, "Payload YAML, skip all prompting") do |val|
|
|
498
|
+
begin
|
|
499
|
+
options[:payload] = YAML.load(val.to_s)
|
|
500
|
+
rescue => ex
|
|
501
|
+
raise ::OptionParser::InvalidOption.new("Failed to parse payload as YAML. Error: #{ex.message}")
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
# --payloads test-data/item*.json
|
|
505
|
+
opts.on('--payloads PATH', String, "Payload(s) from one or more local JSON or YAML files, skip all prompting and execute the request 1-N times, once for each file. PATH can be a directory or a file pattern.") do |val|
|
|
506
|
+
# maybe use parse_array(val) to support csv..
|
|
507
|
+
# find files matching PATH
|
|
508
|
+
# todo: probably support recursive... can be done with '**/*.json' now though.
|
|
509
|
+
if val.to_s.strip.empty?
|
|
510
|
+
raise ::OptionParser::InvalidOption.new("PATH must be provided as directory, file or pattern to find JSON or YAML files.")
|
|
511
|
+
end
|
|
512
|
+
filepath = File.expand_path(val.to_s.strip)
|
|
513
|
+
files = []
|
|
514
|
+
if File.directory?(filepath)
|
|
515
|
+
# passed the name of a directory, include all the JSON and YAML files directly under it
|
|
516
|
+
Dir.glob(File.join(filepath, "*")).each do |file|
|
|
517
|
+
if File.file?(file) && ['.json','.yaml','.yml'].include?(File.extname(file))
|
|
518
|
+
files << file
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
if files.empty?
|
|
522
|
+
raise ::OptionParser::InvalidOption.new("Failed to find any .json or .yaml files under the directory: #{filepath}")
|
|
523
|
+
end
|
|
524
|
+
elsif File.file?(filepath)
|
|
525
|
+
# passed the name of a file
|
|
526
|
+
files << filepath
|
|
527
|
+
else
|
|
528
|
+
# assume it is a pattern to find files with
|
|
529
|
+
files = Dir.glob(filepath)
|
|
530
|
+
if files.empty?
|
|
531
|
+
raise ::OptionParser::InvalidOption.new("Failed to find any files matching path: #{filepath}")
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
# parse files as JSON or YAML
|
|
535
|
+
options[:payload_files] ||= []
|
|
536
|
+
options[:payloads] ||= []
|
|
537
|
+
files.each do |file|
|
|
538
|
+
if options[:payload_files].include?(file)
|
|
539
|
+
next
|
|
540
|
+
else
|
|
541
|
+
options[:payload_files] << file
|
|
542
|
+
end
|
|
543
|
+
payload = nil
|
|
544
|
+
begin
|
|
545
|
+
payload_file = File.expand_path(file)
|
|
546
|
+
if !File.exist?(payload_file) || !File.file?(payload_file)
|
|
547
|
+
raise ::OptionParser::InvalidOption.new("File not found: #{payload_file}")
|
|
548
|
+
end
|
|
549
|
+
# todo: could use parse_json_or_yaml()
|
|
550
|
+
payload = nil
|
|
551
|
+
if payload_file =~ /\.ya?ml\Z/
|
|
552
|
+
payload = YAML.load_file(payload_file)
|
|
553
|
+
else
|
|
554
|
+
payload = JSON.parse(File.read(payload_file))
|
|
555
|
+
end
|
|
556
|
+
options[:payloads] << payload
|
|
557
|
+
rescue => ex
|
|
558
|
+
raise ::OptionParser::InvalidOption.new("Failed to parse payload file: #{payload_file} Error: #{ex.message}")
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
end if all_option_keys.include?(:payloads)
|
|
562
|
+
opts.on('--ignore-payload-errors', "Continue processing any remaining payloads if an error occurs. The default behavior is to stop processing when an error occurs.") do
|
|
563
|
+
options[:ignore_payload_errors] = true
|
|
564
|
+
end if all_option_keys.include?(:payloads)
|
|
565
|
+
when :payloads
|
|
566
|
+
# added with :payload too... just need it here to avoid unknown key error
|
|
567
|
+
# todo: remove this when every command supporting :payload is updated to use parse_payload(options) and execute_api(options)
|
|
568
|
+
when :list
|
|
569
|
+
opts.on( '-m', '--max MAX', "Max Results" ) do |val|
|
|
570
|
+
# api supports max=-1 for all at the moment..
|
|
571
|
+
if val.to_s == "all" || val.to_s == "-1"
|
|
572
|
+
options[:max] = "-1"
|
|
573
|
+
else
|
|
574
|
+
max = val.to_i
|
|
575
|
+
if max <= 0
|
|
576
|
+
raise ::OptionParser::InvalidArgument.new("must be a positive integer")
|
|
577
|
+
end
|
|
578
|
+
options[:max] = max
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
opts.on( '-o', '--offset OFFSET', "Offset Results" ) do |val|
|
|
583
|
+
offset = val.to_i
|
|
584
|
+
if offset <= 0
|
|
585
|
+
raise ::OptionParser::InvalidArgument.new("must be a positive integer")
|
|
586
|
+
end
|
|
587
|
+
options[:offset] = offset
|
|
588
|
+
end
|
|
589
|
+
|
|
590
|
+
if excludes.include?("search") == false
|
|
591
|
+
opts.on( '-s', '--search PHRASE', "Search Phrase" ) do |phrase|
|
|
592
|
+
options[:phrase] = phrase
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
opts.on( '-S', '--sort ORDER', "Sort Order. DIRECTION may be included as \"ORDER [asc|desc]\"." ) do |v|
|
|
597
|
+
if v.to_s.include?(",")
|
|
598
|
+
# sorting on multiple properties, just pass it as is, newer api supports multiple fields
|
|
599
|
+
options[:sort] = v
|
|
600
|
+
else
|
|
601
|
+
v_parts = v.to_s.split(" ")
|
|
602
|
+
if v_parts.size > 1
|
|
603
|
+
options[:sort] = v_parts[0]
|
|
604
|
+
options[:direction] = (v_parts[1].strip == "desc") ? "desc" : "asc"
|
|
605
|
+
else
|
|
606
|
+
options[:sort] = v
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
opts.on( '-D', '--desc', "Descending Sort Direction." ) do |v|
|
|
612
|
+
options[:direction] = "desc"
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
opts.on( "--reverse", "Reverse order of results. This invert is done by the client, not necessarily the entire dataset." ) do
|
|
616
|
+
options[:reverse] = true
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# arbitrary query parameters in the format -Q "category=web&phrase=nginx"
|
|
620
|
+
# opts.on( '-Q', '--query PARAMS', "Query parameters. PARAMS format is 'foo=bar&category=web'" ) do |val|
|
|
621
|
+
# options[:query_filters_raw] = val
|
|
622
|
+
# options[:query_filters] = {}
|
|
623
|
+
# # todo: smarter parsing
|
|
624
|
+
# val.split('&').each do |filter|
|
|
625
|
+
# k, v = filter.split('=')
|
|
626
|
+
# # allow "woot:true instead of woot=true"
|
|
627
|
+
# if (k.include?(":") && v == nil)
|
|
628
|
+
# k, v = k.split(":")
|
|
629
|
+
# end
|
|
630
|
+
# if (!k.to_s.empty?)
|
|
631
|
+
# options[:query_filters][k.to_s.strip] = v.to_s.strip
|
|
632
|
+
# end
|
|
633
|
+
# end
|
|
634
|
+
# end
|
|
635
|
+
|
|
636
|
+
when :query, :query_filters
|
|
637
|
+
# arbitrary query parameters in the format -Q "category=web&phrase=nginx"
|
|
638
|
+
# or pass it many times like -Q foo=bar -Q hello=world
|
|
639
|
+
opts.on( '-Q', '--query PARAMS', "Query parameters. PARAMS format is 'foo=bar&category=web'" ) do |val|
|
|
640
|
+
if options[:query_filters_raw] && !options[:query_filters_raw].empty?
|
|
641
|
+
options[:query_filters_raw] += ("&" + val)
|
|
642
|
+
else
|
|
643
|
+
options[:query_filters_raw] = val
|
|
644
|
+
end
|
|
645
|
+
options[:query_filters] ||= {}
|
|
646
|
+
val.split('&').each do |filter|
|
|
647
|
+
k, v = filter.split('=')
|
|
648
|
+
# allow woot:true instead of woot=true
|
|
649
|
+
if (k.include?(":") && v == nil)
|
|
650
|
+
k, v = k.split(":")
|
|
651
|
+
end
|
|
652
|
+
if (!k.to_s.empty?)
|
|
653
|
+
if options[:query_filters].key?(k.to_s.strip)
|
|
654
|
+
cur_val = options[:query_filters][k.to_s.strip]
|
|
655
|
+
if cur_val.instance_of?(Array)
|
|
656
|
+
options[:query_filters][k.to_s.strip] << v.to_s.strip
|
|
657
|
+
else
|
|
658
|
+
options[:query_filters][k.to_s.strip] = [cur_val, v.to_s.strip]
|
|
659
|
+
end
|
|
660
|
+
else
|
|
661
|
+
options[:query_filters][k.to_s.strip] = v.to_s.strip
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
when :last_updated
|
|
668
|
+
# opts.on("--last-updated TIME", Time, "Filter by gte last updated") do |time|
|
|
669
|
+
opts.on("--last-updated TIME", String, "Filter by Last Updated (gte)") do |time|
|
|
670
|
+
begin
|
|
671
|
+
options[:lastUpdated] = parse_time(time)
|
|
672
|
+
rescue => e
|
|
673
|
+
raise OptionParser::InvalidArgument.new "Failed to parse time '#{time}'. Error: #{e}"
|
|
674
|
+
end
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
when :find_by_name
|
|
678
|
+
opts.on('--find-by-name', "Always treat the identifier argument as a name, never an ID. Useful for specifying names that look like numbers. eg. '1234'" ) do
|
|
679
|
+
options[:find_by_name] = true
|
|
680
|
+
end
|
|
681
|
+
# opts.add_hidden_option('--find-by-name') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
682
|
+
|
|
683
|
+
when :remote
|
|
684
|
+
opts.on( '-r', '--remote REMOTE', "Remote name. The current remote is used by default." ) do |val|
|
|
685
|
+
options[:remote] = val
|
|
686
|
+
end
|
|
687
|
+
opts.on( '--remote-url URL', '--remote-url URL', "Remote url. This allows adhoc requests instead of using a configured remote." ) do |val|
|
|
688
|
+
options[:remote_url] = val
|
|
689
|
+
end
|
|
690
|
+
opts.on( '-T', '--token TOKEN', "Access token for authentication with --remote. Saved credentials are used by default." ) do |val|
|
|
691
|
+
options[:remote_token] = val
|
|
692
|
+
end unless excludes.include?(:remote_token)
|
|
693
|
+
opts.on( '--token-file FILE', String, "Token File, read a file containing the access token." ) do |val|
|
|
694
|
+
token_file = File.expand_path(val)
|
|
695
|
+
if !File.exist?(token_file) || !File.file?(token_file)
|
|
696
|
+
raise ::OptionParser::InvalidOption.new("File not found: #{token_file}")
|
|
697
|
+
end
|
|
698
|
+
options[:remote_token] = File.read(token_file).to_s.split("\n").first.strip
|
|
699
|
+
end
|
|
700
|
+
opts.add_hidden_option('--token-file') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
701
|
+
opts.on( '-U', '--username USERNAME', "Username for authentication." ) do |val|
|
|
702
|
+
options[:remote_username] = val
|
|
703
|
+
end unless excludes.include?(:remote_username)
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
unless excludes.include?(:remote_password)
|
|
707
|
+
opts.on( '-P', '--password PASSWORD', "Password for authentication." ) do |val|
|
|
708
|
+
options[:remote_password] = val
|
|
709
|
+
end
|
|
710
|
+
opts.on( '--password-file FILE', String, "Password File, read a file containing the password for authentication." ) do |val|
|
|
711
|
+
password_file = File.expand_path(val)
|
|
712
|
+
if !File.exist?(password_file) || !File.file?(password_file)
|
|
713
|
+
raise ::OptionParser::InvalidOption.new("File not found: #{password_file}")
|
|
714
|
+
end
|
|
715
|
+
file_content = File.read(password_file) #.strip
|
|
716
|
+
options[:remote_password] = File.read(password_file).to_s.split("\n").first
|
|
717
|
+
end
|
|
718
|
+
opts.add_hidden_option('--password-file') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
# todo: also require this for talking to plain old HTTP
|
|
722
|
+
opts.on('-I','--insecure', "Allow insecure HTTPS communication. i.e. bad SSL certificate.") do |val|
|
|
723
|
+
options[:insecure] = true
|
|
724
|
+
MtnCloud::RestClient.enable_ssl_verification = false
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
#when :header, :headers
|
|
728
|
+
opts.on( '-H', '--header HEADER', "Additional HTTP header to include with requests." ) do |val|
|
|
729
|
+
options[:headers] ||= {}
|
|
730
|
+
# header_list = val.to_s.split(',')
|
|
731
|
+
header_list = [val.to_s]
|
|
732
|
+
header_list.each do |h|
|
|
733
|
+
header_parts = val.to_s.split(":")
|
|
734
|
+
header_key, header_value = header_parts[0], header_parts[1..-1].join(":").strip
|
|
735
|
+
if header_parts.size() < 2
|
|
736
|
+
header_parts = val.to_s.split("=")
|
|
737
|
+
header_key, header_value = header_parts[0], header_parts[1..-1].join("=").strip
|
|
738
|
+
end
|
|
739
|
+
if header_parts.size() < 2
|
|
740
|
+
raise_command_error "Invalid HEADER value '#{val}'. HEADER should contain a key and a value. eg. -H 'X-Morpheus-Lease: $MORPHEUS_LEASE_TOKEN'"
|
|
741
|
+
end
|
|
742
|
+
options[:headers][header_key] = header_value
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
# opts.add_hidden_option('-H') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
746
|
+
# opts.add_hidden_option('--header') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
747
|
+
opts.add_hidden_option('-H, --header') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
748
|
+
|
|
749
|
+
#when :timeout
|
|
750
|
+
opts.on( '--timeout SECONDS', "Timeout for api requests. Default is typically 30 seconds." ) do |val|
|
|
751
|
+
options[:timeout] = val ? val.to_f : nil
|
|
752
|
+
end
|
|
753
|
+
opts.add_hidden_option('--timeout') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
754
|
+
|
|
755
|
+
when :auto_confirm
|
|
756
|
+
opts.on( '-y', '--yes', "Auto Confirm" ) do
|
|
757
|
+
options[:yes] = true
|
|
758
|
+
end
|
|
759
|
+
|
|
760
|
+
when :json
|
|
761
|
+
opts.on('-j','--json', "JSON Output") do
|
|
762
|
+
options[:json] = true
|
|
763
|
+
options[:format] = :json
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
opts.on('--json-raw', String, "JSON Output that is not so pretty.") do |val|
|
|
767
|
+
options[:json] = true
|
|
768
|
+
options[:format] = :json
|
|
769
|
+
options[:pretty_json] = false
|
|
770
|
+
end
|
|
771
|
+
opts.add_hidden_option('json-raw') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
772
|
+
|
|
773
|
+
when :yaml
|
|
774
|
+
# -y for --yes and for --yaml
|
|
775
|
+
if includes.include?(:auto_confirm)
|
|
776
|
+
opts.on(nil, '--yaml', "YAML Output") do
|
|
777
|
+
options[:yaml] = true
|
|
778
|
+
options[:format] = :yaml
|
|
779
|
+
end
|
|
780
|
+
else
|
|
781
|
+
opts.on('-y', '--yaml', "YAML Output") do
|
|
782
|
+
options[:yaml] = true
|
|
783
|
+
options[:format] = :yaml
|
|
784
|
+
end
|
|
785
|
+
end
|
|
786
|
+
opts.on(nil, '--yml', "alias for --yaml") do
|
|
787
|
+
options[:yaml] = true
|
|
788
|
+
options[:format] = :yaml
|
|
789
|
+
end
|
|
790
|
+
opts.add_hidden_option('yml') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
791
|
+
|
|
792
|
+
when :csv
|
|
793
|
+
opts.on(nil, '--csv', "CSV Output") do
|
|
794
|
+
options[:csv] = true
|
|
795
|
+
options[:format] = :csv
|
|
796
|
+
#options[:csv_delim] = options[:csv_delim] || ","
|
|
797
|
+
end
|
|
798
|
+
# deprecated --csv-delim, use --delimiter instead
|
|
799
|
+
opts.on('--csv-delim CHAR', String, "Delimiter for CSV Output values. Default: ','") do |val|
|
|
800
|
+
options[:csv] = true
|
|
801
|
+
options[:format] = :csv
|
|
802
|
+
val = val.gsub("\\n", "\n").gsub("\\r", "\r").gsub("\\t", "\t") if val.include?("\\")
|
|
803
|
+
options[:csv_delim] = val
|
|
804
|
+
end
|
|
805
|
+
opts.add_hidden_option('--csv-delim') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
806
|
+
|
|
807
|
+
# deprecated --csv-newline, use --newline instead
|
|
808
|
+
opts.on('--csv-newline [CHAR]', String, "Delimiter for CSV Output rows. Default: '\\n'") do |val|
|
|
809
|
+
options[:csv] = true
|
|
810
|
+
options[:format] = :csv
|
|
811
|
+
if val == "no" || val == "none"
|
|
812
|
+
options[:csv_newline] = ""
|
|
813
|
+
else
|
|
814
|
+
val = val.to_s.gsub("\\n", "\n").gsub("\\r", "\r").gsub("\\t", "\t") if val.include?("\\")
|
|
815
|
+
options[:csv_newline] = val
|
|
816
|
+
end
|
|
817
|
+
end
|
|
818
|
+
opts.add_hidden_option('--csv-newline') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
819
|
+
|
|
820
|
+
opts.on(nil, '--csv-quotes', "Wrap CSV values with \". Default: false") do
|
|
821
|
+
options[:csv] = true
|
|
822
|
+
options[:format] = :csv
|
|
823
|
+
options[:csv_quotes] = true
|
|
824
|
+
end
|
|
825
|
+
opts.add_hidden_option('--csv-quotes') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
826
|
+
|
|
827
|
+
opts.on(nil, '--csv-no-header', "Exclude header for CSV Output.") do
|
|
828
|
+
options[:csv] = true
|
|
829
|
+
options[:format] = :csv
|
|
830
|
+
options[:csv_no_header] = true
|
|
831
|
+
end
|
|
832
|
+
opts.add_hidden_option('--csv-no-header') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
833
|
+
|
|
834
|
+
opts.on(nil, '--quotes', "Wrap CSV values with \". Default: false") do
|
|
835
|
+
options[:csv_quotes] = true
|
|
836
|
+
end
|
|
837
|
+
opts.add_hidden_option('--csv-quotes') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
838
|
+
|
|
839
|
+
opts.on(nil, '--no-header', "Exclude header for CSV Output.") do
|
|
840
|
+
options[:csv_no_header] = true
|
|
841
|
+
end
|
|
842
|
+
|
|
843
|
+
when :fields
|
|
844
|
+
opts.on('-f', '--fields x,y,z', Array, "Filter Output to a limited set of fields. Default is all fields for json,csv,yaml.") do |val|
|
|
845
|
+
if val.size == 1 && val[0].downcase == 'all'
|
|
846
|
+
options[:all_fields] = true
|
|
847
|
+
else
|
|
848
|
+
options[:include_fields] = val
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
opts.on('-F', '--old-fields x,y,z', Array, "alias for -f, --fields") do |val|
|
|
852
|
+
if val.size == 1 && val[0].downcase == 'all'
|
|
853
|
+
options[:all_fields] = true
|
|
854
|
+
else
|
|
855
|
+
options[:include_fields] = val
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
opts.add_hidden_option('-F, --old-fields') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
859
|
+
opts.on('--raw-fields [x,y,z]', String, "Raw fields filters output like --fields except the properties [x,y,z] must be specified from the root of the response instead of relative to the the list or object context for this particular resource.") do |val|
|
|
860
|
+
if val.size == 1 && val[0].downcase == 'all'
|
|
861
|
+
options[:all_fields] = true
|
|
862
|
+
else
|
|
863
|
+
options[:include_fields] = val.split(',').collect {|r| r.strip}.compact
|
|
864
|
+
end
|
|
865
|
+
options[:raw_fields] = true
|
|
866
|
+
end
|
|
867
|
+
opts.add_hidden_option('--raw-fields') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
868
|
+
opts.on(nil, '--all-fields', "Show all fields present in the data.") do
|
|
869
|
+
options[:all_fields] = true
|
|
870
|
+
end
|
|
871
|
+
opts.on(nil, '--wrap', "Wrap table columns instead hiding them when terminal is not wide enough.") do
|
|
872
|
+
options[:wrap] = true
|
|
873
|
+
end
|
|
874
|
+
when :select
|
|
875
|
+
#opts.add_hidden_option('--all-fields') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
876
|
+
opts.on('--select x,y,z', String, "Filter Output to just print the value(s) of specific fields.") do |val|
|
|
877
|
+
options[:select_fields] = val.split(',').collect {|r| r.strip}
|
|
878
|
+
end
|
|
879
|
+
opts.on('--raw-select x,y,z', String, "Raw select works like --select except the properties [x,y,z] must be specified from the root of the response instead of relative to the the list or object context for this particular resource.") do |val|
|
|
880
|
+
options[:select_fields] = val.split(',').collect {|r| r.strip}
|
|
881
|
+
options[:raw_fields] = true
|
|
882
|
+
end
|
|
883
|
+
opts.add_hidden_option('--raw-select') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
884
|
+
|
|
885
|
+
when :delim
|
|
886
|
+
opts.on('--delimiter [CHAR]', String, "Delimiter for output values. Default: ',', use with --select and --csv") do |val|
|
|
887
|
+
options[:csv] = true
|
|
888
|
+
options[:format] = :csv
|
|
889
|
+
val = val.to_s
|
|
890
|
+
val = val.gsub("\\n", "\n").gsub("\\r", "\r").gsub("\\t", "\t") if val.include?("\\")
|
|
891
|
+
options[:delim] = val
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
opts.on('--newline [CHAR]', String, "Delimiter for output rows. Default: '\\n', use with --select and --csv") do |val|
|
|
895
|
+
options[:csv] = true
|
|
896
|
+
options[:format] = :csv
|
|
897
|
+
val = val.to_s
|
|
898
|
+
if val == "no" || val == "none"
|
|
899
|
+
options[:newline] = ""
|
|
900
|
+
else
|
|
901
|
+
val = val.to_s.gsub("\\n", "\n").gsub("\\r", "\r").gsub("\\t", "\t") if val.include?("\\")
|
|
902
|
+
options[:newline] = val
|
|
903
|
+
end
|
|
904
|
+
end
|
|
905
|
+
when :thin
|
|
906
|
+
opts.on( '--thin', '--thin', "Format headers and columns with thin borders." ) do |val|
|
|
907
|
+
options[:border_style] = :thin
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
when :dry_run
|
|
913
|
+
opts.on('-d','--dry-run', "Dry Run, print the API request instead of executing it.") do
|
|
914
|
+
# todo: this should print after parsing obv..
|
|
915
|
+
# need a hook after parse! or a standard_handle(options) { ... } paradigm
|
|
916
|
+
# either that or hook it up in every command somehow, maybe a hook on connect()
|
|
917
|
+
#puts "#{cyan}#{dark} #=> DRY RUN#{reset}"
|
|
918
|
+
# don't print this for --json combined with -d
|
|
919
|
+
# print once and dont munge json
|
|
920
|
+
if !options[:curl] && !options[:json]
|
|
921
|
+
puts "#{cyan}#{bold}#{dark}DRY RUN#{reset}"
|
|
922
|
+
end
|
|
923
|
+
options[:dry_run] = true
|
|
924
|
+
end
|
|
925
|
+
opts.on(nil,'--curl', "Curl, print the API request as a curl command instead of executing it.") do
|
|
926
|
+
# print once and dont munge json
|
|
927
|
+
if !options[:dry_run] && !options[:json]
|
|
928
|
+
puts "#{cyan}#{bold}#{dark}DRY RUN#{reset}"
|
|
929
|
+
end
|
|
930
|
+
options[:dry_run] = true
|
|
931
|
+
options[:curl] = true
|
|
932
|
+
end
|
|
933
|
+
opts.on(nil,'--scrub', "Mask secrets in output, such as the Authorization header. For use with --curl and --dry-run.") do
|
|
934
|
+
options[:scrub] = true
|
|
935
|
+
end
|
|
936
|
+
# dry run comes with hidden outfile options
|
|
937
|
+
#opts.add_hidden_option('--scrub') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
938
|
+
opts.on('--out FILE', String, "Write standard output to a file instead of the terminal.") do |val|
|
|
939
|
+
# could validate directory is writable..
|
|
940
|
+
options[:outfile] = val
|
|
941
|
+
end
|
|
942
|
+
opts.add_hidden_option('--out') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
943
|
+
opts.on('--overwrite', '--overwrite', "Overwrite output file if it already exists.") do
|
|
944
|
+
options[:overwrite] = true
|
|
945
|
+
end
|
|
946
|
+
opts.add_hidden_option('--overwrite') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
947
|
+
|
|
948
|
+
when :outfile
|
|
949
|
+
opts.on('--out FILE', String, "Write standard output to a file instead of the terminal.") do |val|
|
|
950
|
+
options[:outfile] = val
|
|
951
|
+
end
|
|
952
|
+
opts.on('--overwrite', '--overwrite', "Overwrite output file if it already exists.") do |val|
|
|
953
|
+
options[:overwrite] = true
|
|
954
|
+
end
|
|
955
|
+
when :quiet
|
|
956
|
+
opts.on('-q','--quiet', "No Output, do not print to stdout") do
|
|
957
|
+
options[:quiet] = true
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
else
|
|
961
|
+
raise "Unknown common_option key: #{option_key}"
|
|
962
|
+
end
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
# options that are always included
|
|
966
|
+
|
|
967
|
+
# always support thin, but hidden because mostly not hooked up at the moment...
|
|
968
|
+
unless includes.include?(:thin)
|
|
969
|
+
opts.on( '--thin', '--thin', "Format headers and columns with thin borders." ) do |val|
|
|
970
|
+
options[:border_style] = :thin
|
|
971
|
+
end
|
|
972
|
+
opts.add_hidden_option('--thin') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
# disable ANSI coloring
|
|
976
|
+
opts.on('-C','--nocolor', "Disable ANSI coloring") do
|
|
977
|
+
Term::ANSIColor::coloring = false
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
# Benchmark this command?
|
|
982
|
+
# Also useful for seeing exit status for every command.
|
|
983
|
+
opts.on('-B','--benchmark', "Print benchmark time and exit/error after the command is finished.") do
|
|
984
|
+
options[:benchmark] = true
|
|
985
|
+
# this is hacky, but working!
|
|
986
|
+
# shell handles returning to false
|
|
987
|
+
#MtnCloud::Benchmarking.enabled = true
|
|
988
|
+
#my_terminal.benchmarking = true
|
|
989
|
+
#start_benchmark(args.join(' '))
|
|
990
|
+
# ok it happens outside of handle() alltogether..
|
|
991
|
+
# wow, simplify me plz
|
|
992
|
+
end
|
|
993
|
+
|
|
994
|
+
opts.on('-V','--debug', "Print extra output for debugging.") do
|
|
995
|
+
options[:debug] = true
|
|
996
|
+
MtnCloud::Logging.set_log_level(MtnCloud::Logging::Logger::DEBUG)
|
|
997
|
+
::RestClient.log = MtnCloud::Logging.debug? ? MtnCloud::Logging::DarkPrinter.instance : nil
|
|
998
|
+
# perhaps...
|
|
999
|
+
# create a new logger instance just for this command instance
|
|
1000
|
+
# this way we don't elevate the global level for subsequent commands in a shell
|
|
1001
|
+
# @logger = MtnCloud::Logging::Logger.new(STDOUT)
|
|
1002
|
+
# if !@logger.debug?
|
|
1003
|
+
# @logger.log_level = MtnCloud::Logging::Logger::DEBUG
|
|
1004
|
+
# end
|
|
1005
|
+
end
|
|
1006
|
+
|
|
1007
|
+
# A way to ensure debugging is off, it should go back on after the command is complete.
|
|
1008
|
+
opts.on('--no-debug','--no-debug', "Disable debugging.") do
|
|
1009
|
+
options[:debug] = false
|
|
1010
|
+
MtnCloud::Logging.set_log_level(MtnCloud::Logging::Logger::INFO)
|
|
1011
|
+
::RestClient.log = MtnCloud::Logging.debug? ? MtnCloud::Logging::DarkPrinter.instance : nil
|
|
1012
|
+
end
|
|
1013
|
+
opts.add_hidden_option('--no-debug') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
1014
|
+
|
|
1015
|
+
opts.on('--hidden-help', "Print help that includes all the hidden options, like this one." ) do
|
|
1016
|
+
puts opts.full_help_message({show_hidden_options:true})
|
|
1017
|
+
exit 0 # return 0 maybe?
|
|
1018
|
+
end
|
|
1019
|
+
opts.add_hidden_option('--hidden-help') if opts.is_a?(MtnCloud::Cli::OptionParser)
|
|
1020
|
+
opts.on('-h', '--help', "Print this help" ) do
|
|
1021
|
+
puts opts
|
|
1022
|
+
exit 0 # return 0 maybe?
|
|
1023
|
+
end
|
|
1024
|
+
|
|
1025
|
+
opts
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
def prog_name
|
|
1029
|
+
self.class.prog_name
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
def command_name
|
|
1033
|
+
self.class.command_name
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
def command_description
|
|
1037
|
+
self.class.command_description
|
|
1038
|
+
end
|
|
1039
|
+
|
|
1040
|
+
def subcommands
|
|
1041
|
+
self.class.subcommands
|
|
1042
|
+
end
|
|
1043
|
+
|
|
1044
|
+
def visible_subcommands
|
|
1045
|
+
self.class.visible_subcommands
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
def subcommand_aliases
|
|
1049
|
+
self.class.subcommand_aliases
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
# def subcommand_descriptions
|
|
1053
|
+
# self.class.subcommand_descriptions
|
|
1054
|
+
# end
|
|
1055
|
+
|
|
1056
|
+
def get_subcommand_description(subcmd)
|
|
1057
|
+
self.class.get_subcommand_description(subcmd)
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
# The name of the command method that called subcommand_usage or
|
|
1061
|
+
# subcommand_description, for example "get" when called from Clusters#get.
|
|
1062
|
+
# Before Ruby 3.4 the label is "get" or "block in get". From Ruby 3.4 it
|
|
1063
|
+
# includes the class, as in "MtnCloud::Cli::Clusters#get".
|
|
1064
|
+
def calling_method_name
|
|
1065
|
+
label = caller_locations(2, 1)[0].label.to_s
|
|
1066
|
+
label = label.sub(/\Ablock (\(\d+ levels\) )?in /, '')
|
|
1067
|
+
label.split(/[#.]/).last.to_s
|
|
1068
|
+
end
|
|
1069
|
+
|
|
1070
|
+
def subcommand_description()
|
|
1071
|
+
calling_method = calling_method_name
|
|
1072
|
+
subcommand_name = subcommands.key(calling_method)
|
|
1073
|
+
subcommand_name ? get_subcommand_description(subcommand_name) : nil
|
|
1074
|
+
end
|
|
1075
|
+
|
|
1076
|
+
def default_subcommand
|
|
1077
|
+
self.class.default_subcommand
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
def default_refresh_interval
|
|
1081
|
+
self.class.default_refresh_interval
|
|
1082
|
+
end
|
|
1083
|
+
|
|
1084
|
+
def usage
|
|
1085
|
+
if !subcommands.empty?
|
|
1086
|
+
"Usage: #{prog_name} #{command_name} [command] [options]"
|
|
1087
|
+
else
|
|
1088
|
+
"Usage: #{prog_name} #{command_name} [options]"
|
|
1089
|
+
end
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
def my_help_command
|
|
1093
|
+
"#{prog_name} #{command_name} --help"
|
|
1094
|
+
end
|
|
1095
|
+
|
|
1096
|
+
def subcommand_usage(*extra)
|
|
1097
|
+
calling_method = calling_method_name
|
|
1098
|
+
subcommand_name = subcommands.key(calling_method)
|
|
1099
|
+
extra = extra.flatten
|
|
1100
|
+
if !subcommand_name.empty? && extra.first == subcommand_name
|
|
1101
|
+
extra.shift
|
|
1102
|
+
end
|
|
1103
|
+
#extra = ["[options]"] if extra.empty?
|
|
1104
|
+
"Usage: #{prog_name} #{command_name} #{subcommand_name} #{extra.join(' ')}".squeeze(' ').strip
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
# a string to describe the usage of your command
|
|
1108
|
+
# this is what the --help option
|
|
1109
|
+
# feel free to override this in your commands
|
|
1110
|
+
def full_command_usage
|
|
1111
|
+
out = ""
|
|
1112
|
+
out << usage.to_s.strip if usage
|
|
1113
|
+
out << "\n"
|
|
1114
|
+
my_subcommands = visible_subcommands
|
|
1115
|
+
if !my_subcommands.empty?
|
|
1116
|
+
out << "Commands:"
|
|
1117
|
+
out << "\n"
|
|
1118
|
+
my_subcommands.sort.each {|subcmd, method|
|
|
1119
|
+
desc = get_subcommand_description(subcmd)
|
|
1120
|
+
out << "\t#{subcmd.to_s}"
|
|
1121
|
+
out << "\t#{desc}" if desc
|
|
1122
|
+
out << "\n"
|
|
1123
|
+
}
|
|
1124
|
+
end
|
|
1125
|
+
if command_description && !command_description.to_s.strip.empty?
|
|
1126
|
+
out << "\n"
|
|
1127
|
+
out << "#{command_description.strip}\n"
|
|
1128
|
+
end
|
|
1129
|
+
# out << "\n"
|
|
1130
|
+
out
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
# a default handler
|
|
1134
|
+
def handle_subcommand(args)
|
|
1135
|
+
commands = subcommands
|
|
1136
|
+
if subcommands.empty?
|
|
1137
|
+
raise "#{self.class} has no available subcommands"
|
|
1138
|
+
end
|
|
1139
|
+
# meh, could deprecate and make subcommand define handle() itself
|
|
1140
|
+
# if args.count == 0 && default_subcommand
|
|
1141
|
+
# # p "using default subcommand #{default_subcommand}"
|
|
1142
|
+
# return send(default_subcommand, args || [])
|
|
1143
|
+
# end
|
|
1144
|
+
subcommand_name = args[0]
|
|
1145
|
+
if args.empty?
|
|
1146
|
+
print_error MtnCloud::Terminal.angry_prompt
|
|
1147
|
+
puts_error "[command] argument is required"
|
|
1148
|
+
puts full_command_usage
|
|
1149
|
+
exit 127
|
|
1150
|
+
end
|
|
1151
|
+
if args[0] == "-h" || args[0] == "--help" || args[0] == "help"
|
|
1152
|
+
puts full_command_usage
|
|
1153
|
+
return 0
|
|
1154
|
+
end
|
|
1155
|
+
if subcommand_aliases[subcommand_name]
|
|
1156
|
+
subcommand_name = subcommand_aliases[subcommand_name]
|
|
1157
|
+
end
|
|
1158
|
+
cmd_method = subcommands[subcommand_name]
|
|
1159
|
+
if !cmd_method
|
|
1160
|
+
error_msg = "'#{command_name} #{subcommand_name}' is not a known command.\n#{full_command_usage}"
|
|
1161
|
+
raise CommandNotFoundError.new(error_msg)
|
|
1162
|
+
end
|
|
1163
|
+
send(cmd_method, args[1..-1])
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
def handle(args)
|
|
1167
|
+
raise "#{self} has not defined handle()!"
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
# executes block with each argument in the list
|
|
1171
|
+
# @return [0|1] 0 if they were all successful, else 1
|
|
1172
|
+
def run_command_for_each_arg(args, &block)
|
|
1173
|
+
cmd_results = []
|
|
1174
|
+
args.each do |arg|
|
|
1175
|
+
begin
|
|
1176
|
+
cur_result = yield arg
|
|
1177
|
+
rescue SystemExit => err
|
|
1178
|
+
cur_result = err.success? ? 0 : 1
|
|
1179
|
+
end
|
|
1180
|
+
cmd_results << cur_result
|
|
1181
|
+
end
|
|
1182
|
+
# find a bad result and return it
|
|
1183
|
+
cmd_results = cmd_results.collect do |cmd_result|
|
|
1184
|
+
if cmd_result.is_a?(Array)
|
|
1185
|
+
cmd_result
|
|
1186
|
+
else
|
|
1187
|
+
[cmd_result, nil]
|
|
1188
|
+
end
|
|
1189
|
+
end
|
|
1190
|
+
failed_result = cmd_results.find {|cmd_result| cmd_result[0] == false || (cmd_result[0].is_a?(Integer) && cmd_result[0] != 0) }
|
|
1191
|
+
return failed_result ? failed_result : cmd_results.last
|
|
1192
|
+
end
|
|
1193
|
+
|
|
1194
|
+
# def connect(options={})
|
|
1195
|
+
# MtnCloud::Logging::DarkPrinter.puts "#{command_name} has not defined connect()" if MtnCloud::Logging.debug?
|
|
1196
|
+
# end
|
|
1197
|
+
|
|
1198
|
+
# This supports the simple remote option eg. `instances add --remote "qa"`
|
|
1199
|
+
# It will establish a connection to the pre-configured appliance named "qa"
|
|
1200
|
+
# By default it will connect to the active (current) remote appliance
|
|
1201
|
+
# This returns a new instance of MtnCloud::APIClient (and sets @access_token, and @appliance)
|
|
1202
|
+
# Your command should be ready to make api requests after this.
|
|
1203
|
+
# This will prompt for credentials if none are found, use :skip_login
|
|
1204
|
+
# Credentials will be saved unless --remote-url or --token is being used.
|
|
1205
|
+
def establish_remote_appliance_connection(options)
|
|
1206
|
+
# todo: probably refactor and don't rely on this method to set these instance vars
|
|
1207
|
+
@remote_appliance = nil
|
|
1208
|
+
@appliance_name, @appliance_url, @access_token = nil, nil, nil
|
|
1209
|
+
@api_client = nil
|
|
1210
|
+
@do_save_credentials = true
|
|
1211
|
+
# skip saving if --remote-url or --username or --password are passed in
|
|
1212
|
+
if options[:remote_url] || options[:remote_token] || options[:remote_username] || options[:remote_password]
|
|
1213
|
+
@do_save_credentials = false
|
|
1214
|
+
end
|
|
1215
|
+
appliance = nil
|
|
1216
|
+
if options[:remote_url]
|
|
1217
|
+
# --remote-url means use an arbitrary url, do not save any appliance config
|
|
1218
|
+
# appliance = {name:'remote-url', url:options[:remote_url]}
|
|
1219
|
+
appliance = {url:options[:remote_url]}
|
|
1220
|
+
appliance[:temporary] = true
|
|
1221
|
+
#appliance[:status] = "ready" # or "unknown"
|
|
1222
|
+
# appliance[:last_check] = nil
|
|
1223
|
+
elsif options[:remote]
|
|
1224
|
+
# --remote means use the specified remote
|
|
1225
|
+
appliance = ::MtnCloud::Cli::Remote.load_remote(options[:remote])
|
|
1226
|
+
if appliance.nil?
|
|
1227
|
+
if ::MtnCloud::Cli::Remote.appliances.empty?
|
|
1228
|
+
raise_command_error "No remote appliances exist, see the command `remote add`."
|
|
1229
|
+
else
|
|
1230
|
+
raise_command_error "Remote appliance not found by the name '#{options[:remote]}', see `remote list`"
|
|
1231
|
+
end
|
|
1232
|
+
end
|
|
1233
|
+
else
|
|
1234
|
+
# use active remote
|
|
1235
|
+
appliance = ::MtnCloud::Cli::Remote.load_active_remote()
|
|
1236
|
+
if !appliance
|
|
1237
|
+
if ::MtnCloud::Cli::Remote.appliances.empty?
|
|
1238
|
+
raise_command_error "No remote appliances exist, see the command `remote add`"
|
|
1239
|
+
else
|
|
1240
|
+
raise_command_error "#{command_name} requires a remote to be specified, try the option -r [remote] or see the command `remote use`"
|
|
1241
|
+
end
|
|
1242
|
+
end
|
|
1243
|
+
end
|
|
1244
|
+
@remote_appliance = appliance
|
|
1245
|
+
@appliance_name = appliance[:name]
|
|
1246
|
+
@appliance_url = appliance[:url] || appliance[:host] # it used to store :host in the YAML
|
|
1247
|
+
# set enable_ssl_verification
|
|
1248
|
+
# instead of toggling this global value
|
|
1249
|
+
# this should just be an attribute of the api client
|
|
1250
|
+
# for now, this fixes the issue where passing --insecure or --remote
|
|
1251
|
+
# would then apply to all subsequent commands...
|
|
1252
|
+
allow_insecure = false
|
|
1253
|
+
if options[:insecure] || appliance[:insecure] || MtnCloud::Cli::Shell.insecure
|
|
1254
|
+
allow_insecure = true
|
|
1255
|
+
end
|
|
1256
|
+
@verify_ssl = !allow_insecure
|
|
1257
|
+
# MtnCloud::RestClient.enable_ssl_verification = allow_insecure != true
|
|
1258
|
+
if allow_insecure && MtnCloud::RestClient.ssl_verification_enabled?
|
|
1259
|
+
MtnCloud::RestClient.enable_ssl_verification = false
|
|
1260
|
+
elsif !allow_insecure && !MtnCloud::RestClient.ssl_verification_enabled?
|
|
1261
|
+
MtnCloud::RestClient.enable_ssl_verification = true
|
|
1262
|
+
end
|
|
1263
|
+
|
|
1264
|
+
# always support accepting --username and --password on the command line
|
|
1265
|
+
# it's probably better not to do that tho, just so it stays out of history files
|
|
1266
|
+
|
|
1267
|
+
# if !@appliance_name && !@appliance_url
|
|
1268
|
+
# raise_command_error "Please specify a remote appliance with -r or see the command `remote use`"
|
|
1269
|
+
# end
|
|
1270
|
+
|
|
1271
|
+
MtnCloud::Logging::DarkPrinter.puts "establishing connection to remote #{display_appliance(@appliance_name, @appliance_url)}" if MtnCloud::Logging.debug? # && !options[:quiet]
|
|
1272
|
+
|
|
1273
|
+
if options[:no_authorization]
|
|
1274
|
+
# maybe handle this here..
|
|
1275
|
+
options[:skip_login] = true
|
|
1276
|
+
options[:skip_verify_access_token] = true
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
# ok, get some credentials.
|
|
1280
|
+
# use saved credentials by default or prompts for username, password.
|
|
1281
|
+
# passing --remote-url will skip loading saved credentials and prompt for login to use with the url
|
|
1282
|
+
# passing --token skips login prompting and uses the provided token.
|
|
1283
|
+
# passing --token or --username will skip saving credentials to appliance config, they are just used for one command
|
|
1284
|
+
# ideally this should not prompt now and wait until the client is used on a protected endpoint.
|
|
1285
|
+
# @wallet = nil
|
|
1286
|
+
if options[:remote_token]
|
|
1287
|
+
@wallet = {'access_token' => options[:remote_token]} #'username' => 'anonymous'
|
|
1288
|
+
elsif options[:remote_url]
|
|
1289
|
+
credentials = MtnCloud::Cli::Credentials.new(@appliance_name, @appliance_url)
|
|
1290
|
+
unless options[:skip_login]
|
|
1291
|
+
@wallet = credentials.request_credentials(options.merge({dry_run:false}), @do_save_credentials)
|
|
1292
|
+
end
|
|
1293
|
+
else
|
|
1294
|
+
credentials = MtnCloud::Cli::Credentials.new(@appliance_name, @appliance_url)
|
|
1295
|
+
# use saved credentials unless --username or passed
|
|
1296
|
+
unless options[:remote_username]
|
|
1297
|
+
@wallet = credentials.load_saved_credentials()
|
|
1298
|
+
end
|
|
1299
|
+
# using active remote OR --remote flag
|
|
1300
|
+
# used saved credentials or login
|
|
1301
|
+
# ideally this sould not prompt now and wait until the client is used on a protected endpoint.
|
|
1302
|
+
|
|
1303
|
+
|
|
1304
|
+
if @wallet.nil? || @wallet['access_token'].nil?
|
|
1305
|
+
unless options[:skip_login]
|
|
1306
|
+
@wallet = credentials.request_credentials(options.merge({dry_run:false}), @do_save_credentials)
|
|
1307
|
+
end
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
end
|
|
1311
|
+
@access_token = @wallet ? @wallet['access_token'] : nil
|
|
1312
|
+
|
|
1313
|
+
# validate we have a token
|
|
1314
|
+
# hrm...
|
|
1315
|
+
unless options[:skip_verify_access_token]
|
|
1316
|
+
if @access_token.empty?
|
|
1317
|
+
raise AuthorizationRequiredError.new("Failed to acquire access token for #{display_appliance(@appliance_name, @appliance_url)}. Verify your credentials are correct.")
|
|
1318
|
+
end
|
|
1319
|
+
end
|
|
1320
|
+
|
|
1321
|
+
# ok, connect to the appliance.. actually this just instantiates an ApiClient
|
|
1322
|
+
api_client = MtnCloud::APIClient.new(@access_token,nil,nil, @appliance_url, @verify_ssl)
|
|
1323
|
+
@api_client = api_client # meh, just return w/o setting instance attrs
|
|
1324
|
+
return api_client
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
# verify_args! verifies that the right number of commands were passed
|
|
1328
|
+
# and raises a command error if not.
|
|
1329
|
+
# Example: verify_args!(args:args, count:1, optparse:optparse)
|
|
1330
|
+
# this could go be done in optparse.parse instead perhaps
|
|
1331
|
+
def verify_args!(opts={})
|
|
1332
|
+
args = opts[:args] || []
|
|
1333
|
+
count = opts[:count]
|
|
1334
|
+
# simplify output for verify_args!(min:2, max:2) or verify_args!(max:0)
|
|
1335
|
+
if opts[:min] && opts[:max] && opts[:min] == opts[:max]
|
|
1336
|
+
count = opts[:min]
|
|
1337
|
+
elsif opts[:max] == 0
|
|
1338
|
+
count = 0
|
|
1339
|
+
end
|
|
1340
|
+
if count
|
|
1341
|
+
if args.count < count
|
|
1342
|
+
raise_args_error("not enough arguments, expected #{count} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
|
|
1343
|
+
elsif args.count > count
|
|
1344
|
+
raise_args_error("too many arguments, expected #{count} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
|
|
1345
|
+
end
|
|
1346
|
+
else
|
|
1347
|
+
if opts[:min]
|
|
1348
|
+
if args.count < opts[:min]
|
|
1349
|
+
raise_args_error("not enough arguments, expected #{opts[:min] || '0'}-#{opts[:max] || 'N'} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
|
|
1350
|
+
end
|
|
1351
|
+
end
|
|
1352
|
+
if opts[:max]
|
|
1353
|
+
if args.count > opts[:max]
|
|
1354
|
+
raise_args_error("too many arguments, expected #{opts[:min] || '0'}-#{opts[:max] || 'N'} and got #{args.count == 0 ? '0' : args.count.to_s + ': '}#{args.join(', ')}", args, opts[:optparse])
|
|
1355
|
+
end
|
|
1356
|
+
end
|
|
1357
|
+
end
|
|
1358
|
+
true
|
|
1359
|
+
end
|
|
1360
|
+
|
|
1361
|
+
def confirm(msg, options)
|
|
1362
|
+
options[:yes] or ::MtnCloud::Cli::OptionTypes::confirm(msg, options)
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
def confirm!(msg, options)
|
|
1366
|
+
confirm(msg, options) or raise CommandAborted.new("confirmation declined: #{msg}")
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
# The default way to build options for the list command
|
|
1370
|
+
# @param [OptionParser] opts
|
|
1371
|
+
# @param [Hash] options
|
|
1372
|
+
# @param [Hash] params
|
|
1373
|
+
def build_list_options(opts, options, params)
|
|
1374
|
+
build_standard_list_options(opts, options)
|
|
1375
|
+
end
|
|
1376
|
+
|
|
1377
|
+
# The default way to parse options for the list command
|
|
1378
|
+
# @param [Array] args
|
|
1379
|
+
# @param [Hash] options
|
|
1380
|
+
# @param [Hash] params
|
|
1381
|
+
def parse_list_options!(args, options, params)
|
|
1382
|
+
if args.count > 0
|
|
1383
|
+
options[:phrase] = args.join(" ")
|
|
1384
|
+
# params['phrase'] = = args.join(" ")
|
|
1385
|
+
end
|
|
1386
|
+
params.merge!(parse_list_options(options))
|
|
1387
|
+
# query parameters are stored in :params
|
|
1388
|
+
# preserve anything already set in :params by the OptionParser or command specific logic..
|
|
1389
|
+
options[:params] ||= {}
|
|
1390
|
+
options[:params].deep_merge!(params)
|
|
1391
|
+
return options
|
|
1392
|
+
end
|
|
1393
|
+
|
|
1394
|
+
# The default way to build options for the list command
|
|
1395
|
+
# @param [OptionParser] opts
|
|
1396
|
+
# @param [Hash] options
|
|
1397
|
+
# @param [Hash] params
|
|
1398
|
+
def build_get_options(opts, options, params)
|
|
1399
|
+
build_standard_get_options(opts, options)
|
|
1400
|
+
end
|
|
1401
|
+
|
|
1402
|
+
# The default way to parse options for the get command
|
|
1403
|
+
# @param [OptionParser] opts
|
|
1404
|
+
# @param [Hash] options
|
|
1405
|
+
# @param [Hash] params
|
|
1406
|
+
def parse_get_options!(args, options, params)
|
|
1407
|
+
params.merge!(parse_query_options(options))
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
# Add a name=value query parameter, building an array if there is more than one
|
|
1411
|
+
# @param [Hash] params the query parameter map to append to
|
|
1412
|
+
# @param [String] key the parameter name
|
|
1413
|
+
# @param [Object] value the parameter value
|
|
1414
|
+
def add_query_parameter(params, key, value)
|
|
1415
|
+
# key = key.to_s
|
|
1416
|
+
if params.key?(key) && !params[key].nil?
|
|
1417
|
+
if !params[key].is_a?(Array)
|
|
1418
|
+
params[key] = [params[key]]
|
|
1419
|
+
end
|
|
1420
|
+
params[key] << value
|
|
1421
|
+
else
|
|
1422
|
+
params[key] = value
|
|
1423
|
+
end
|
|
1424
|
+
params
|
|
1425
|
+
end
|
|
1426
|
+
|
|
1427
|
+
# The default way to parse options for the get command
|
|
1428
|
+
# @param type [string]
|
|
1429
|
+
# @param options [Hash] The command options
|
|
1430
|
+
# @param params [Hash] The query parameters the output is being appended to
|
|
1431
|
+
# @param param_name [String]
|
|
1432
|
+
# @param lookup_ids [Boolean] Also lookup ids to make sure they exist or else error
|
|
1433
|
+
# @return
|
|
1434
|
+
def parse_parameter_as_resource_id!(type, options, params, param_name=nil, lookup_ids=false)
|
|
1435
|
+
# type = type.to_s.singularize
|
|
1436
|
+
if options.key?(type)
|
|
1437
|
+
val = options[type].to_s
|
|
1438
|
+
param_name ||= "#{type.to_s.camelcase}Id"
|
|
1439
|
+
if val
|
|
1440
|
+
if val.to_s !~ /\A\d{1,}\Z/ || lookup_ids
|
|
1441
|
+
record = find_by_name(type, val)
|
|
1442
|
+
if record.nil?
|
|
1443
|
+
# avoid double error render by exiting here, ew
|
|
1444
|
+
exit 1
|
|
1445
|
+
raise_command_error "#{type.titleize} not found for '#{val}'"
|
|
1446
|
+
end
|
|
1447
|
+
params[param_name] = record['id']
|
|
1448
|
+
else
|
|
1449
|
+
params[param_name] = val
|
|
1450
|
+
end
|
|
1451
|
+
return params[param_name]
|
|
1452
|
+
end
|
|
1453
|
+
end
|
|
1454
|
+
return nil
|
|
1455
|
+
end
|
|
1456
|
+
|
|
1457
|
+
# parse the parameters provided by the common :list options
|
|
1458
|
+
# this includes the :query options too via parse_query_options().
|
|
1459
|
+
# returns Hash of params the format {"phrase": => "foobar", "max": 100}
|
|
1460
|
+
def parse_list_options(options={})
|
|
1461
|
+
list_params = {}
|
|
1462
|
+
[:phrase, :offset, :max, :sort, :direction, :lastUpdated].each do |k|
|
|
1463
|
+
if options.key?(k)
|
|
1464
|
+
list_params[k.to_s] = options[k]
|
|
1465
|
+
elsif options.key?(k.to_s)
|
|
1466
|
+
list_params[k.to_s] = options[k.to_s]
|
|
1467
|
+
end
|
|
1468
|
+
end
|
|
1469
|
+
# arbitrary filters
|
|
1470
|
+
list_params.merge!(parse_query_options(options))
|
|
1471
|
+
# ok, any string keys in options can become query parameters, eg. options['name'] = 'foobar'
|
|
1472
|
+
# do it!
|
|
1473
|
+
# options.each do |k, v|
|
|
1474
|
+
# list_params[k] = v
|
|
1475
|
+
# end
|
|
1476
|
+
return list_params
|
|
1477
|
+
end
|
|
1478
|
+
|
|
1479
|
+
# parse the parameters provided by the common :query (:query_filters) options
|
|
1480
|
+
# returns Hash of params the format {"phrase": => "foobar", "max": 100}
|
|
1481
|
+
def parse_query_options(options={})
|
|
1482
|
+
list_params = {}
|
|
1483
|
+
# arbitrary filters
|
|
1484
|
+
if options[:query_filters]
|
|
1485
|
+
options[:query_filters].each do |k, v|
|
|
1486
|
+
if k
|
|
1487
|
+
list_params[k] = v
|
|
1488
|
+
end
|
|
1489
|
+
end
|
|
1490
|
+
end
|
|
1491
|
+
return list_params
|
|
1492
|
+
end
|
|
1493
|
+
|
|
1494
|
+
# parse the subtitles provided by the common :list options
|
|
1495
|
+
# returns Array of subtitles as strings in the format ["Phrase: blah", "Max: 100"]
|
|
1496
|
+
def parse_list_subtitles(options={})
|
|
1497
|
+
subtitles = []
|
|
1498
|
+
if options[:tenant]
|
|
1499
|
+
subtitles << "Tenant: #{options[:tenant]}".strip
|
|
1500
|
+
end
|
|
1501
|
+
if options[:include_tenants]
|
|
1502
|
+
subtitles << "Include Tenants: true".strip
|
|
1503
|
+
end
|
|
1504
|
+
[:phrase, :offset, :max, :sort, :direction, :lastUpdated].each do |k|
|
|
1505
|
+
if options.key?(k)
|
|
1506
|
+
subtitles << "#{k.to_s}: #{options[k]}"
|
|
1507
|
+
elsif options.key?(k.to_s)
|
|
1508
|
+
subtitles << "#{k.to_s}: #{options[k.to_s]}"
|
|
1509
|
+
end
|
|
1510
|
+
end
|
|
1511
|
+
# arbitrary filters
|
|
1512
|
+
if options[:query_filters]
|
|
1513
|
+
formatted_filters = options[:query_filters].collect {|k,v| "#{k.to_s}=#{v}" }.join('&')
|
|
1514
|
+
subtitles << "Query: #{formatted_filters}"
|
|
1515
|
+
# options[:query_filters].each do |k, v|
|
|
1516
|
+
# subtitles << "#{k.to_s}: #{v}"
|
|
1517
|
+
# end
|
|
1518
|
+
end
|
|
1519
|
+
return subtitles
|
|
1520
|
+
end
|
|
1521
|
+
|
|
1522
|
+
# Construct the request query parameters from the standard command options
|
|
1523
|
+
# This populates :params with a map of parameters.
|
|
1524
|
+
# This should replace the use of parse_query_options, parse_list_options and parse_list_options! and parse_get_options
|
|
1525
|
+
# which are used everywhere eg. params.merge!(parse_query_options(options))
|
|
1526
|
+
def parse_options(options, params={})
|
|
1527
|
+
params = params ? params.dup : {}
|
|
1528
|
+
# parse_list_options!(args, options, params)
|
|
1529
|
+
# merge in options set with -Q max=3, --query max=3&sort=id
|
|
1530
|
+
params.deep_merge!(options[:query_filters]) if options[:query_filters]
|
|
1531
|
+
# query parameters are stored in :params
|
|
1532
|
+
# preserve anything already set in :params by the OptionParser or command specific logic..
|
|
1533
|
+
options[:params] ||= {}
|
|
1534
|
+
options[:params].deep_merge!(params)
|
|
1535
|
+
# (JSON) body parameters (JSON) are stored in :payload
|
|
1536
|
+
# if options[:payload]
|
|
1537
|
+
# end
|
|
1538
|
+
# ok now call execute_request(@api_client.whoami, :get, nil, options)
|
|
1539
|
+
return options
|
|
1540
|
+
end
|
|
1541
|
+
|
|
1542
|
+
# Parse payload(s) from the standard command options or else invoke the given block.
|
|
1543
|
+
# First looks for --payload or --payload options and if they are nil then the block is executed to establish the payload
|
|
1544
|
+
# By default this also merges all the values passed with -O, --options foo="bar" into payload under the object_key context.
|
|
1545
|
+
# and they are merged under the object_key context (if passed). This can be disabled with apply_options: false
|
|
1546
|
+
#
|
|
1547
|
+
# @param options [Hash] standard command options
|
|
1548
|
+
# @option options [Hash] :payload is a Hash of objects to serialize as the payload
|
|
1549
|
+
# @option options [Hash] :payloads is an array of payload objects@yield [street_name] Invokes the block with a street name for eac
|
|
1550
|
+
# This is silly and should go away, instead you should iterate in the terminal environment
|
|
1551
|
+
# @option options [Boolean] :apply_options can be set to false to skip -O options merge
|
|
1552
|
+
# @param object_key [String] The name of the object being constructed, -O --options will be merged under this context.
|
|
1553
|
+
# @return array of payloads
|
|
1554
|
+
# @yield [payload] Invokes the block to establish :payload (only when --payload(s) is not used)
|
|
1555
|
+
def parse_payload(options, object_key=nil, &block)
|
|
1556
|
+
# populate options[:params] here too
|
|
1557
|
+
parse_options(options)
|
|
1558
|
+
payloads = []
|
|
1559
|
+
# todo: only need to support a a single payload here, :payloads is silly and is going away
|
|
1560
|
+
if options[:payload]
|
|
1561
|
+
# --payload option was used
|
|
1562
|
+
payload = options[:payload]
|
|
1563
|
+
# support -O OPTION switch on top of --payload
|
|
1564
|
+
apply_options(payload, options, object_key) unless options[:apply_options] == false
|
|
1565
|
+
payloads << payload
|
|
1566
|
+
elsif options[:payloads]
|
|
1567
|
+
# --payloads option was used
|
|
1568
|
+
payloads = options[:payloads]
|
|
1569
|
+
# support -O OPTION switch on top of --payloads
|
|
1570
|
+
payloads.each do |payload|
|
|
1571
|
+
apply_options(payload, options, object_key) unless options[:apply_options] == false
|
|
1572
|
+
end
|
|
1573
|
+
#else
|
|
1574
|
+
# should always do this, but a lot of methods rely on this returning nil right now, not {}
|
|
1575
|
+
# so for now only do it if block is given
|
|
1576
|
+
elsif block_given?
|
|
1577
|
+
# yield to block to construct the payload,
|
|
1578
|
+
# this is typically where prompting for inputs with optionTypes happens
|
|
1579
|
+
payload = {}
|
|
1580
|
+
apply_options(payload, options, object_key) unless options[:apply_options] == false
|
|
1581
|
+
if block_given?
|
|
1582
|
+
yield payload
|
|
1583
|
+
end
|
|
1584
|
+
payloads << payload
|
|
1585
|
+
options[:payload] = payload
|
|
1586
|
+
end
|
|
1587
|
+
return payloads.first
|
|
1588
|
+
end
|
|
1589
|
+
|
|
1590
|
+
# support -O OPTION switch
|
|
1591
|
+
def apply_options(payload, options, object_key=nil)
|
|
1592
|
+
payload ||= {}
|
|
1593
|
+
if options[:options]
|
|
1594
|
+
# allow options[:object_key] to be used
|
|
1595
|
+
object_key = object_key ? object_key : options[:object_key]
|
|
1596
|
+
# could use parse_passed_options() here to support exclusion of certain options
|
|
1597
|
+
#passed_options = parse_passed_options(options, options[:apply_options] || {})
|
|
1598
|
+
passed_options = options[:options].reject {|k,v| k.is_a?(Symbol)}
|
|
1599
|
+
if options[:apply_options_exclude]
|
|
1600
|
+
passed_options = options[:options].reject {|k,v| options[:skip_apply_options].include?(k.to_s) || options[:skip_apply_options].include?(k.to_sym) }
|
|
1601
|
+
end
|
|
1602
|
+
if object_key
|
|
1603
|
+
payload.deep_merge!({object_key => passed_options})
|
|
1604
|
+
else
|
|
1605
|
+
payload.deep_merge!(passed_options)
|
|
1606
|
+
end
|
|
1607
|
+
end
|
|
1608
|
+
payload
|
|
1609
|
+
end
|
|
1610
|
+
|
|
1611
|
+
# Executes the block with each payload (:payload or :payloads as parsed from --payload FILE and --payloads --PATH)
|
|
1612
|
+
# This is a wrapper to support execution on 1-N payloads
|
|
1613
|
+
# It also looks for --ignore-payload-errors behavior to continue processing
|
|
1614
|
+
# It is up to the block to actually make the api request
|
|
1615
|
+
# @param options [Hash] standard command options
|
|
1616
|
+
# @raise [Error] if there is no :payload or :payloads defined.
|
|
1617
|
+
# @yield [payload] Yields each payload to the block
|
|
1618
|
+
# @return parsed command result of the last return value of the block ie. [0, nil]
|
|
1619
|
+
def handle_each_payload(options, &block)
|
|
1620
|
+
payloads = []
|
|
1621
|
+
if options[:payloads]
|
|
1622
|
+
payloads = options[:payloads]
|
|
1623
|
+
elsif options[:payload]
|
|
1624
|
+
payloads << options[:payload]
|
|
1625
|
+
else
|
|
1626
|
+
raise "handle_each_payload() requires :payload or :payloads"
|
|
1627
|
+
end
|
|
1628
|
+
if !payloads.is_a?(Array) || payloads.compact.empty?
|
|
1629
|
+
raise "handle_each_payload() requires a payload"
|
|
1630
|
+
end
|
|
1631
|
+
if !block_given?
|
|
1632
|
+
raise "handle_each_payload() requires a block to process the payload(s) with"
|
|
1633
|
+
end
|
|
1634
|
+
results = []
|
|
1635
|
+
payloads.each do |payload|
|
|
1636
|
+
begin
|
|
1637
|
+
result = yield payload
|
|
1638
|
+
results << MtnCloud::Cli::CliRegistry.parse_command_result(result)
|
|
1639
|
+
#results << [0, nil]
|
|
1640
|
+
rescue => e
|
|
1641
|
+
if options[:payloads_ignore_error]
|
|
1642
|
+
# results << [1, e.message]
|
|
1643
|
+
result = MtnCloud::Cli::ErrorHandler.new(my_terminal.stderr).handle_error(e) # lol
|
|
1644
|
+
results << result
|
|
1645
|
+
# continue
|
|
1646
|
+
else
|
|
1647
|
+
raise e
|
|
1648
|
+
end
|
|
1649
|
+
end
|
|
1650
|
+
end
|
|
1651
|
+
return results.last
|
|
1652
|
+
end
|
|
1653
|
+
|
|
1654
|
+
# Standard handler for all commands that execute an api request.
|
|
1655
|
+
# This looks for a payload that can be set with --payload or --payloads or the default prompting
|
|
1656
|
+
# It is up to the block to handle the rendering behavior
|
|
1657
|
+
# @param api_interface [APIClient] An APIClient instance
|
|
1658
|
+
# @param api_method [String or Symbol] api method to invoke eg. :get, :create, :update, :destroy
|
|
1659
|
+
# @param args [Array] Array of arguments to be passed to the api method, usually just the [payload] or [payload, query_params]
|
|
1660
|
+
# @param options [Hash] options
|
|
1661
|
+
# @param object_key [String or Symbol] name of object being constructed, used by default rendering eg. --fields id,name
|
|
1662
|
+
# @yield [json_response] Invokes the block with the json response to handle rendering.
|
|
1663
|
+
# @return parsed command result of the last block.call(json_response)
|
|
1664
|
+
#
|
|
1665
|
+
# @example Fetch first 100 backups
|
|
1666
|
+
# execute_api(@api_client.backups, :list, [{"max" => 100}], options) do |json_response|
|
|
1667
|
+
# print_green_success "Fetched first #{json_response['backups'].size} of #{json_response['meta']['total']} backups"
|
|
1668
|
+
# end
|
|
1669
|
+
#
|
|
1670
|
+
# @example Create a backup, uses POST with options[:payload] as the body
|
|
1671
|
+
# @options[:payload] = {"backup" => { }}
|
|
1672
|
+
# execute_api(@api_client.backups, :create, nil, options, 'backup') do |json_response|
|
|
1673
|
+
# print_green_success "Added backup #{json_response['backup']['name']}"
|
|
1674
|
+
# end
|
|
1675
|
+
#
|
|
1676
|
+
def execute_api(api_interface, api_method, args, options, object_key=nil, &block)
|
|
1677
|
+
args = args.is_a?(Array) ? args : [args].compact
|
|
1678
|
+
if options[:payload] || options[:payloads]
|
|
1679
|
+
execute_api_payload(api_interface, api_method, args, options, object_key, &block)
|
|
1680
|
+
else
|
|
1681
|
+
execute_api_request(api_interface, api_method, args, options, object_key, &block)
|
|
1682
|
+
end
|
|
1683
|
+
end
|
|
1684
|
+
|
|
1685
|
+
# Standard handler for all POST commands that send a request for a payload
|
|
1686
|
+
# This supports the --payloads option for 1-N payloads, that is silly and will probably go away
|
|
1687
|
+
def execute_api_payload(api_interface, api_method, args, options, object_key=nil, &block)
|
|
1688
|
+
handle_each_payload(options) do |payload|
|
|
1689
|
+
execute_api_request(api_interface, api_method, (args || []) + [payload], options, object_key, &block)
|
|
1690
|
+
end
|
|
1691
|
+
end
|
|
1692
|
+
|
|
1693
|
+
# Standard handler for executing any API request
|
|
1694
|
+
# Supports the --dry-run option and standard rendering options --json, --yaml, --fields, --select, etc.
|
|
1695
|
+
def execute_api_request(api_interface, api_method, args, options, object_key=nil, &block)
|
|
1696
|
+
args = args.is_a?(Array) ? args : [args].compact # allow caller to pass [payload] or payload
|
|
1697
|
+
api_interface.setopts(options) # this is needed to support --timeout and --headers
|
|
1698
|
+
# this assumes the interface parameter order is: [payload, params] and not vice versa
|
|
1699
|
+
if options[:params] && !options[:params].empty?
|
|
1700
|
+
args << options[:params]
|
|
1701
|
+
end
|
|
1702
|
+
if options[:dry_run]
|
|
1703
|
+
# this is a dry run
|
|
1704
|
+
dry_response = api_interface.dry.send(api_method, *args)
|
|
1705
|
+
print_dry_run(dry_response, options)
|
|
1706
|
+
return 0, nil
|
|
1707
|
+
else
|
|
1708
|
+
# execute the request and render the result
|
|
1709
|
+
json_response = api_interface.send(api_method, *args)
|
|
1710
|
+
return render_response(json_response, options, object_key, &block)
|
|
1711
|
+
end
|
|
1712
|
+
end
|
|
1713
|
+
|
|
1714
|
+
# Parse an array from a string (csv)
|
|
1715
|
+
def parse_array(val, opts={})
|
|
1716
|
+
opts = {strip:true, allow_blank:false}.merge(opts)
|
|
1717
|
+
values = []
|
|
1718
|
+
if val.is_a?(Array)
|
|
1719
|
+
values = val
|
|
1720
|
+
else
|
|
1721
|
+
# parse csv and strip white space by default
|
|
1722
|
+
values = val.to_s.split(",")#.collect {|it| it.to_s.strip }
|
|
1723
|
+
if opts[:strip]
|
|
1724
|
+
values = values.collect {|it| it.to_s.strip }
|
|
1725
|
+
end
|
|
1726
|
+
if !opts[:allow_blank]
|
|
1727
|
+
values.reject! {|it| it.to_s.strip.empty? }
|
|
1728
|
+
end
|
|
1729
|
+
end
|
|
1730
|
+
values
|
|
1731
|
+
end
|
|
1732
|
+
|
|
1733
|
+
def parse_labels(val)
|
|
1734
|
+
parse_array(val, {strip:true, allow_blank:false})
|
|
1735
|
+
end
|
|
1736
|
+
|
|
1737
|
+
def validate_outfile(outfile, options)
|
|
1738
|
+
full_filename = File.expand_path(outfile)
|
|
1739
|
+
outdir = File.dirname(full_filename)
|
|
1740
|
+
if Dir.exist?(full_filename)
|
|
1741
|
+
print_red_alert "[local-file] is invalid. It is the name of an existing directory: #{outfile}"
|
|
1742
|
+
return false
|
|
1743
|
+
end
|
|
1744
|
+
if !Dir.exist?(outdir)
|
|
1745
|
+
if options[:mkdir]
|
|
1746
|
+
print cyan,"Creating local directory #{outdir}",reset,"\n"
|
|
1747
|
+
FileUtils.mkdir_p(outdir)
|
|
1748
|
+
else
|
|
1749
|
+
print_red_alert "[local-file] is invalid. Directory not found: #{outdir}"
|
|
1750
|
+
return false
|
|
1751
|
+
end
|
|
1752
|
+
end
|
|
1753
|
+
if File.exist?(full_filename) && !options[:overwrite]
|
|
1754
|
+
print_red_alert "[local-file] is invalid. File already exists: #{outfile}\nUse -f to overwrite the existing file."
|
|
1755
|
+
return false
|
|
1756
|
+
end
|
|
1757
|
+
return true
|
|
1758
|
+
end
|
|
1759
|
+
|
|
1760
|
+
# basic rendering for options :json, :yml, :csv, :quiet, and :outfile
|
|
1761
|
+
# returns the string rendered, or nil if nothing was rendered.
|
|
1762
|
+
def render_response(json_response, options, object_key=nil, &block)
|
|
1763
|
+
# allow options[:object_key] to be used
|
|
1764
|
+
object_key = object_key ? object_key : options[:object_key]
|
|
1765
|
+
output = nil
|
|
1766
|
+
if options[:select_fields]
|
|
1767
|
+
# support foos get --raw-select foo.x,foo.y,foo.z
|
|
1768
|
+
# and foos list --raw-select foos.x,foos.y,foos.z
|
|
1769
|
+
row = (object_key && !options[:raw_fields]) ? json_response[object_key] : json_response
|
|
1770
|
+
records = [row].flatten()
|
|
1771
|
+
# look for an array in the first field only now...
|
|
1772
|
+
field_parts = options[:select_fields][0].to_s.split(".")
|
|
1773
|
+
field_context = field_parts[0]
|
|
1774
|
+
context_data = json_response[field_context]
|
|
1775
|
+
if field_parts.size > 1 && context_data.is_a?(Array)
|
|
1776
|
+
# inject all the root level properties to be selectable too..
|
|
1777
|
+
context_data = json_response.delete(field_context)
|
|
1778
|
+
# records = context_data
|
|
1779
|
+
records = context_data.collect {|it| it.is_a?(Hash) ? json_response.merge(it) : json_response }
|
|
1780
|
+
options[:select_fields] = options[:select_fields].collect {|it| it.sub(field_context+'.', '')}
|
|
1781
|
+
end
|
|
1782
|
+
output = records.collect { |record|
|
|
1783
|
+
options[:select_fields].collect { |field|
|
|
1784
|
+
value = get_object_value(record, field)
|
|
1785
|
+
if value.is_a?(String)
|
|
1786
|
+
value
|
|
1787
|
+
else
|
|
1788
|
+
if options[:json]
|
|
1789
|
+
as_json(value, options)
|
|
1790
|
+
elsif options[:yaml]
|
|
1791
|
+
output = as_yaml(value, options)
|
|
1792
|
+
elsif options[:csv]
|
|
1793
|
+
as_csv(value, nil, options)
|
|
1794
|
+
else
|
|
1795
|
+
# default behavior
|
|
1796
|
+
# value.is_a?(String) ? value : as_json(value, options)
|
|
1797
|
+
do_pretty = options.key?(:pretty_json) ? options[:pretty_json] : false # or true?
|
|
1798
|
+
do_pretty ? JSON.pretty_generate(value) : JSON.fast_generate(value)
|
|
1799
|
+
end
|
|
1800
|
+
end
|
|
1801
|
+
}.join(options[:delim] || ",")
|
|
1802
|
+
}.join(options[:newline] || "\n")
|
|
1803
|
+
elsif options[:json]
|
|
1804
|
+
output = as_json(json_response, options, object_key)
|
|
1805
|
+
elsif options[:yaml]
|
|
1806
|
+
output = as_yaml(json_response, options, object_key)
|
|
1807
|
+
elsif options[:csv]
|
|
1808
|
+
output = as_csv(json_response, nil, options, object_key)
|
|
1809
|
+
end
|
|
1810
|
+
if options[:outfile]
|
|
1811
|
+
full_outfile = File.expand_path(options[:outfile])
|
|
1812
|
+
if output
|
|
1813
|
+
print_to_file(output, options[:outfile], options[:overwrite])
|
|
1814
|
+
print "#{cyan}Wrote output to file #{options[:outfile]} (#{format_bytes(File.size(full_outfile))})\n" unless options[:quiet]
|
|
1815
|
+
else
|
|
1816
|
+
# uhhh ok lets try this
|
|
1817
|
+
MtnCloud::Logging::DarkPrinter.puts "using experimental feature: --out without a common format like json, yml or csv" if MtnCloud::Logging.debug?
|
|
1818
|
+
result = with_stdout_to_file(options[:outfile], options[:overwrite], 'w+', &block)
|
|
1819
|
+
if result && result != 0
|
|
1820
|
+
return result
|
|
1821
|
+
end
|
|
1822
|
+
print "#{cyan}Wrote output to file #{options[:outfile]} (#{format_bytes(File.size(full_outfile))})\n" unless options[:quiet]
|
|
1823
|
+
return 0, nil
|
|
1824
|
+
end
|
|
1825
|
+
else
|
|
1826
|
+
# --quiet means do not render, still want to print to outfile though
|
|
1827
|
+
if options[:quiet]
|
|
1828
|
+
return 0, nil
|
|
1829
|
+
end
|
|
1830
|
+
# render ouput generated above
|
|
1831
|
+
if output
|
|
1832
|
+
puts output
|
|
1833
|
+
return 0, nil
|
|
1834
|
+
else
|
|
1835
|
+
# no render happened, so calling the block if given
|
|
1836
|
+
if block_given?
|
|
1837
|
+
result = yield json_response
|
|
1838
|
+
if result
|
|
1839
|
+
return result
|
|
1840
|
+
else
|
|
1841
|
+
return 0, nil
|
|
1842
|
+
end
|
|
1843
|
+
else
|
|
1844
|
+
# nil means nothing was rendered, some methods still using render_with_format() are relying on this
|
|
1845
|
+
return nil
|
|
1846
|
+
end
|
|
1847
|
+
end
|
|
1848
|
+
end
|
|
1849
|
+
end
|
|
1850
|
+
|
|
1851
|
+
alias :render_with_format :render_response
|
|
1852
|
+
|
|
1853
|
+
# Dynamic find methods to load a record of any type
|
|
1854
|
+
# def find_by_name_or_id(type, val)
|
|
1855
|
+
# interface = instance_variable_get "@#{type}s_interface"
|
|
1856
|
+
# typeCamelCase = type.gsub(/(?:^|_)([a-z])/) do $1.upcase end
|
|
1857
|
+
# typeCamelCase = typeCamelCase[0, 1].downcase + typeCamelCase[1..-1]
|
|
1858
|
+
# (val.to_s =~ /\A\d{1,}\Z/) ? interface.get(val.to_i)[typeCamelCase] : interface.list({'name' => val})["#{typeCamelCase}s"].first
|
|
1859
|
+
# end
|
|
1860
|
+
|
|
1861
|
+
# Find a resource by type and name or id
|
|
1862
|
+
# @param type [String of Symbol] Type of resource formatted as singular, lowerscore with underscores.
|
|
1863
|
+
# @param *id [String or Numeric] ID of resource, multiple arguments may be passed when using a secondary interface where parent_id, id is required.
|
|
1864
|
+
# Example: find_by_name_or_id("instance", "K2")
|
|
1865
|
+
# find_by_name_or_id("storage_volume", 42)
|
|
1866
|
+
# find_by_name_or_id("instance", "My Instance")
|
|
1867
|
+
# find_by_name_or_id("load_balancer_pool", load_balancer_id, id)
|
|
1868
|
+
def find_by_name_or_id(*args)
|
|
1869
|
+
val = args.last
|
|
1870
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
|
1871
|
+
return find_by_id(*args)
|
|
1872
|
+
else
|
|
1873
|
+
return find_by_name(*args)
|
|
1874
|
+
end
|
|
1875
|
+
end
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
# Find a resource by type and id
|
|
1879
|
+
# Usage: find_by_name_or_id("app", 3)
|
|
1880
|
+
def find_by_id(*args)
|
|
1881
|
+
#MtnCloud::Logging::DarkPrinter.puts "find_by_id(#{args.join(', ')})" if MtnCloud::Logging.debug?
|
|
1882
|
+
# type, ids = args.first, args[1..-1]
|
|
1883
|
+
type, *ids = args
|
|
1884
|
+
type = type.to_s.singularize.underscore
|
|
1885
|
+
# still relying on the command or helper to define these _label and _key methods
|
|
1886
|
+
# label = send("#{type}_label")
|
|
1887
|
+
# object_key = send("#{type}_object_key")
|
|
1888
|
+
# ^ nope, not for long!
|
|
1889
|
+
object_key = respond_to?("#{type}_object_key", true) ? send("#{type}_object_key") : type.camelcase.singularize
|
|
1890
|
+
label = respond_to?("#{type}_label", true) ? send("#{type}_label") : type.titleize
|
|
1891
|
+
interface_name = "@#{type.pluralize}_interface"
|
|
1892
|
+
interface = instance_variable_get(interface_name)
|
|
1893
|
+
if interface.nil?
|
|
1894
|
+
api_client = instance_variable_get("@api_client")
|
|
1895
|
+
if api_client
|
|
1896
|
+
if api_client.respond_to?(type.pluralize)
|
|
1897
|
+
interface = api_client.send(type.pluralize)
|
|
1898
|
+
else
|
|
1899
|
+
raise "@api_client.#{type.pluralize} is not a recognized interface"
|
|
1900
|
+
end
|
|
1901
|
+
else
|
|
1902
|
+
raise "#{self.class} has not defined interface #{interface_name} or @api_client"
|
|
1903
|
+
end
|
|
1904
|
+
end
|
|
1905
|
+
begin
|
|
1906
|
+
json_response = interface.get(*ids)
|
|
1907
|
+
if !json_response.key?(object_key)
|
|
1908
|
+
# maybe just use the first key like this:
|
|
1909
|
+
# object_key = json_response.keys.find { |k| json_response[k].is_a?(Hash) }
|
|
1910
|
+
#puts_error(json_response) if MtnCloud::Logging.debug?
|
|
1911
|
+
raise "API response is missing object property '#{object_key}'"
|
|
1912
|
+
end
|
|
1913
|
+
record = json_response[object_key]
|
|
1914
|
+
if record.nil?
|
|
1915
|
+
print_red_alert "#{label} not found in API response (#{object_key})"
|
|
1916
|
+
return nil
|
|
1917
|
+
else
|
|
1918
|
+
return json_response[object_key]
|
|
1919
|
+
end
|
|
1920
|
+
rescue ::RestClient::Exception => e
|
|
1921
|
+
if e.response && e.response.code == 404
|
|
1922
|
+
print_red_alert "#{label} not found by id #{ids.last}"
|
|
1923
|
+
return nil
|
|
1924
|
+
else
|
|
1925
|
+
raise e
|
|
1926
|
+
end
|
|
1927
|
+
end
|
|
1928
|
+
end
|
|
1929
|
+
|
|
1930
|
+
# Find a record by type and name
|
|
1931
|
+
# Usage: find_by_name_or_id("network", "Skynet")
|
|
1932
|
+
def find_by_name(*args)
|
|
1933
|
+
#MtnCloud::Logging::DarkPrinter.puts "find_by_name(#{args.join(', ')})" if MtnCloud::Logging.debug?
|
|
1934
|
+
# type, ids = args.first, args[1..-1]
|
|
1935
|
+
type, *ids = args
|
|
1936
|
+
type = type.to_s.singularize.underscore
|
|
1937
|
+
val = ids.pop
|
|
1938
|
+
params = {}
|
|
1939
|
+
name_property = 'name'
|
|
1940
|
+
if type == 'user'
|
|
1941
|
+
name_property = 'username'
|
|
1942
|
+
params['global'] = 'true'
|
|
1943
|
+
end
|
|
1944
|
+
params[name_property] = val.to_s
|
|
1945
|
+
request_args = ids + [params]
|
|
1946
|
+
request_args.unshift(type)
|
|
1947
|
+
records = find_all(*request_args)
|
|
1948
|
+
# still relying on the command or helper to define these _label and _key methods
|
|
1949
|
+
label = respond_to?("#{type}_label", true) ? send("#{type}_label") : type.titleize
|
|
1950
|
+
if records.empty?
|
|
1951
|
+
print_red_alert "#{label} not found by name '#{val}'"
|
|
1952
|
+
return nil
|
|
1953
|
+
elsif records.size > 1
|
|
1954
|
+
print_red_alert "More than one #{label.downcase} found by #{name_property} '#{val}'"
|
|
1955
|
+
print_error "\n"
|
|
1956
|
+
if type == "user"
|
|
1957
|
+
puts_error as_pretty_table(records, [:id, :username, {"FIRST NAME" => "firstName"}, {"LAST NAME" => "lastName"}, {"TENANT" => lambda {|it| it['account']['name'] rescue ''}}], {color:red})
|
|
1958
|
+
else
|
|
1959
|
+
puts_error as_pretty_table(records, [:id, :name], {color:red})
|
|
1960
|
+
end
|
|
1961
|
+
print_red_alert "Try using ID instead"
|
|
1962
|
+
print_error reset,"\n"
|
|
1963
|
+
return nil
|
|
1964
|
+
else
|
|
1965
|
+
return records[0]
|
|
1966
|
+
end
|
|
1967
|
+
end
|
|
1968
|
+
|
|
1969
|
+
# Load a list of records by type
|
|
1970
|
+
# @example Find an app by id
|
|
1971
|
+
# find_record(:app, 1)
|
|
1972
|
+
# report_types = find_all("reportTypes", {phrase:"amazon"})
|
|
1973
|
+
# pools = find_all("loadBalancerPool", load_balancer_id)
|
|
1974
|
+
# @return Array of records
|
|
1975
|
+
def find_all(*args)
|
|
1976
|
+
#MtnCloud::Logging::DarkPrinter.puts "find_all(#{args.join(', ')})" if MtnCloud::Logging.debug?
|
|
1977
|
+
type, *request_args = args
|
|
1978
|
+
type = type.to_s.singularize.underscore
|
|
1979
|
+
list_key = respond_to?("#{type}_list_key", true) ? send("#{type}_list_key") : get_list_key(type)
|
|
1980
|
+
json_response = find_all_json(*args)
|
|
1981
|
+
if !json_response.key?(list_key)
|
|
1982
|
+
# maybe just use the first key like this:
|
|
1983
|
+
# list_key = json_response.keys.find { |k| json_response[k].is_a?(Array) }
|
|
1984
|
+
# puts_error(json_response) if MtnCloud::Logging.debug?
|
|
1985
|
+
raise "API response is missing list property '#{list_key}'"
|
|
1986
|
+
end
|
|
1987
|
+
return json_response[list_key]
|
|
1988
|
+
end
|
|
1989
|
+
|
|
1990
|
+
# Load json response for a list of records by type
|
|
1991
|
+
# @return Hash of JSON data
|
|
1992
|
+
def find_all_json(*args)
|
|
1993
|
+
type, *request_args = args
|
|
1994
|
+
get_interface(type).list(*request_args)
|
|
1995
|
+
end
|
|
1996
|
+
|
|
1997
|
+
alias :find_all_records :find_all
|
|
1998
|
+
alias :find_all_records_json :find_all_json
|
|
1999
|
+
|
|
2000
|
+
# Load a single record (Hash) by type and id and optional parameters
|
|
2001
|
+
# Examples:
|
|
2002
|
+
# apps = find_record(:app)
|
|
2003
|
+
# report_types = find_record("reportTypes", {phrase:"amazon"})
|
|
2004
|
+
# pools = find_record("loadBalancerPool", balancer_id)
|
|
2005
|
+
# @return [Hash] of the object that was found or raises an exception if 404 not found encountered.
|
|
2006
|
+
def find_record(*args)
|
|
2007
|
+
#MtnCloud::Logging::DarkPrinter.puts "find_record(#{args.join(', ')})" if MtnCloud::Logging.debug?
|
|
2008
|
+
type, *request_args = args
|
|
2009
|
+
type = type.to_s.singularize.underscore
|
|
2010
|
+
object_key = respond_to?("#{type}_object_key", true) ? send("#{type}_object_key") : get_object_key(type)
|
|
2011
|
+
json_response = find_record_json(*args)
|
|
2012
|
+
if !json_response.key?(object_key)
|
|
2013
|
+
# maybe just use the first key like this:
|
|
2014
|
+
# object_key = json_response.keys.find { |k| json_response[k].is_a?(Hash) }
|
|
2015
|
+
# puts_error(json_response) if MtnCloud::Logging.debug?
|
|
2016
|
+
raise "API response is missing object property '#{object_key}'"
|
|
2017
|
+
end
|
|
2018
|
+
return json_response[object_key]
|
|
2019
|
+
end
|
|
2020
|
+
|
|
2021
|
+
# Load list of records by type and (optional) parameters
|
|
2022
|
+
# Examples:
|
|
2023
|
+
# apps = find_record(:app, 1)
|
|
2024
|
+
# report_types = find_record("reportType", 1)
|
|
2025
|
+
# pools = find_all("loadBalancerPool", load_balancer_id)
|
|
2026
|
+
def find_record_json(*args)
|
|
2027
|
+
#MtnCloud::Logging::DarkPrinter.puts "find_record_json(#{args.join(', ')})" if MtnCloud::Logging.debug?
|
|
2028
|
+
type, *request_args = args
|
|
2029
|
+
get_interface(type).get(*request_args)
|
|
2030
|
+
end
|
|
2031
|
+
|
|
2032
|
+
# Load json response for a list of records by type
|
|
2033
|
+
def get_interface(type)
|
|
2034
|
+
#MtnCloud::Logging::DarkPrinter.puts "get_interface(#{type})" if MtnCloud::Logging.debug?
|
|
2035
|
+
# todo: can probably just do @api_client ? @api_client.interface(interface_name) : nil
|
|
2036
|
+
type = type.to_s.singularize.underscore
|
|
2037
|
+
interface_name = "@#{type.pluralize}_interface"
|
|
2038
|
+
interface = nil
|
|
2039
|
+
if instance_variable_defined?(interface_name)
|
|
2040
|
+
interface = instance_variable_get(interface_name)
|
|
2041
|
+
if interface.nil?
|
|
2042
|
+
# Fix is to update connect() to do @apps_interface = @api_client.apps
|
|
2043
|
+
raise "API Interface #{interface_name} is nil"
|
|
2044
|
+
end
|
|
2045
|
+
else
|
|
2046
|
+
if @api_client.is_a?(MtnCloud::APIClient)
|
|
2047
|
+
interface = @api_client.interface(type.pluralize)
|
|
2048
|
+
else
|
|
2049
|
+
raise "#{self.class} has not initalized @api_client"
|
|
2050
|
+
end
|
|
2051
|
+
end
|
|
2052
|
+
return interface
|
|
2053
|
+
end
|
|
2054
|
+
|
|
2055
|
+
def get_list_key(type)
|
|
2056
|
+
return get_object_key(type).pluralize
|
|
2057
|
+
end
|
|
2058
|
+
|
|
2059
|
+
def get_object_key(type)
|
|
2060
|
+
key = type.camelcase.singularize
|
|
2061
|
+
# add aliases here as needed
|
|
2062
|
+
if key == "cloud"
|
|
2063
|
+
key = "zone"
|
|
2064
|
+
elsif key == "site"
|
|
2065
|
+
key = "group"
|
|
2066
|
+
elsif key == "backupJob"
|
|
2067
|
+
key = "job"
|
|
2068
|
+
elsif key == "backupResult"
|
|
2069
|
+
key = "result"
|
|
2070
|
+
elsif key == "backupRestore"
|
|
2071
|
+
key = "restore"
|
|
2072
|
+
end
|
|
2073
|
+
return key
|
|
2074
|
+
end
|
|
2075
|
+
|
|
2076
|
+
module ClassMethods
|
|
2077
|
+
|
|
2078
|
+
# attr_writer :command_name, :command_description, :hidden_command, :default_refresh_interval,
|
|
2079
|
+
# :subcommands, :hidden_subcommands, :default_subcommand, :subcommand_aliases, :subcommand_descriptions
|
|
2080
|
+
|
|
2081
|
+
def prog_name
|
|
2082
|
+
MtnCloud::Cli.prog_name
|
|
2083
|
+
end
|
|
2084
|
+
|
|
2085
|
+
def set_command_name(cmd_name)
|
|
2086
|
+
@command_name = cmd_name.to_sym
|
|
2087
|
+
MtnCloud::Cli::CliRegistry.add(self, self.command_name)
|
|
2088
|
+
end
|
|
2089
|
+
|
|
2090
|
+
def default_command_name
|
|
2091
|
+
class_name = self.name.split('::')[-1]
|
|
2092
|
+
#class_name.sub!(/Command$/, '')
|
|
2093
|
+
MtnCloud::Cli::CliRegistry.cli_ize(class_name).to_sym
|
|
2094
|
+
end
|
|
2095
|
+
|
|
2096
|
+
def command_name
|
|
2097
|
+
@command_name ||= default_command_name
|
|
2098
|
+
@command_name
|
|
2099
|
+
end
|
|
2100
|
+
|
|
2101
|
+
def set_command_hidden(val=true)
|
|
2102
|
+
@hidden_command = val
|
|
2103
|
+
end
|
|
2104
|
+
# alias :command_name= :set_command_name
|
|
2105
|
+
|
|
2106
|
+
def hidden_command
|
|
2107
|
+
defined?(@hidden_command) && @hidden_command == true
|
|
2108
|
+
end
|
|
2109
|
+
|
|
2110
|
+
def hidden_subcommands
|
|
2111
|
+
@hidden_subcommands ||= []
|
|
2112
|
+
end
|
|
2113
|
+
|
|
2114
|
+
def set_subcommands_hidden(*cmds)
|
|
2115
|
+
@hidden_subcommands ||= []
|
|
2116
|
+
cmds.flatten.each do |cmd|
|
|
2117
|
+
@hidden_subcommands << cmd.to_sym
|
|
2118
|
+
end
|
|
2119
|
+
@hidden_subcommands
|
|
2120
|
+
end
|
|
2121
|
+
|
|
2122
|
+
def command_description
|
|
2123
|
+
@command_description ||= ""
|
|
2124
|
+
end
|
|
2125
|
+
|
|
2126
|
+
def set_command_description(val)
|
|
2127
|
+
@command_description = val
|
|
2128
|
+
end
|
|
2129
|
+
# alias :command_description= :set_command_description
|
|
2130
|
+
|
|
2131
|
+
def default_refresh_interval
|
|
2132
|
+
@default_refresh_interval ||= 30
|
|
2133
|
+
end
|
|
2134
|
+
|
|
2135
|
+
def set_default_refresh_interval(seconds)
|
|
2136
|
+
@default_refresh_interval = seconds
|
|
2137
|
+
end
|
|
2138
|
+
#alias :default_refresh_interval= :set_default_refresh_interval
|
|
2139
|
+
|
|
2140
|
+
|
|
2141
|
+
# construct map of command name => instance method
|
|
2142
|
+
def register_subcommands(*cmds)
|
|
2143
|
+
@subcommands ||= {}
|
|
2144
|
+
cmds.flatten.each {|cmd|
|
|
2145
|
+
if cmd.is_a?(Hash)
|
|
2146
|
+
cmd.each {|k,v|
|
|
2147
|
+
# @subcommands[k] = v
|
|
2148
|
+
add_subcommand(k.to_s, v.to_s)
|
|
2149
|
+
}
|
|
2150
|
+
elsif cmd.is_a?(Array)
|
|
2151
|
+
cmd.each {|it| register_subcommands(it) }
|
|
2152
|
+
elsif cmd.is_a?(String) || cmd.is_a?(Symbol)
|
|
2153
|
+
#k = MtnCloud::Cli::CliRegistry.cli_ize(cmd)
|
|
2154
|
+
k = cmd.to_s.gsub('_', '-')
|
|
2155
|
+
v = cmd.to_s.gsub('-', '_')
|
|
2156
|
+
register_subcommands({(k) => v})
|
|
2157
|
+
else
|
|
2158
|
+
raise MtnCloud::Cli::CliRegistry::BadCommandDefinition.new("Unable to register command of type: #{cmd.class} #{cmd}")
|
|
2159
|
+
end
|
|
2160
|
+
}
|
|
2161
|
+
return
|
|
2162
|
+
end
|
|
2163
|
+
|
|
2164
|
+
# this might be the new hotness
|
|
2165
|
+
# register_subcommand(:show) # do not do this, always define a description!
|
|
2166
|
+
# register_subcommand(:list, "List things")
|
|
2167
|
+
# register_subcommand("update-all", "update_all", "Update all things")
|
|
2168
|
+
# If the command name =~ method, no need to pass both
|
|
2169
|
+
# command names will have "-" swapped in for "_" and vice versa for method names.
|
|
2170
|
+
def register_subcommand(*args)
|
|
2171
|
+
args = args.flatten
|
|
2172
|
+
cmd_name = args[0]
|
|
2173
|
+
cmd_method = nil
|
|
2174
|
+
cmd_desc = nil
|
|
2175
|
+
if args.count == 1
|
|
2176
|
+
cmd_method = cmd_name
|
|
2177
|
+
elsif args.count == 2
|
|
2178
|
+
if args[1].is_a?(Symbol)
|
|
2179
|
+
cmd_method = args[1]
|
|
2180
|
+
else
|
|
2181
|
+
cmd_method = cmd_name
|
|
2182
|
+
cmd_desc = args[1]
|
|
2183
|
+
end
|
|
2184
|
+
elsif args.count == 3
|
|
2185
|
+
cmd_method = args[1]
|
|
2186
|
+
cmd_desc = args[2]
|
|
2187
|
+
else
|
|
2188
|
+
raise MtnCloud::Cli::CliRegistry::BadCommandDefinition.new("register_subcommand expects 1-3 arguments, got #{args.size} #{args.inspect}")
|
|
2189
|
+
end
|
|
2190
|
+
cmd_name = cmd_name.to_s.gsub("_", "-").to_sym
|
|
2191
|
+
cmd_method = (cmd_method || cmd_name).to_s.gsub("-", "_").to_sym
|
|
2192
|
+
cmd_definition = {(cmd_name) => cmd_method}
|
|
2193
|
+
register_subcommands(cmd_definition)
|
|
2194
|
+
add_subcommand_description(cmd_name, cmd_desc)
|
|
2195
|
+
return
|
|
2196
|
+
end
|
|
2197
|
+
|
|
2198
|
+
def set_default_subcommand(cmd)
|
|
2199
|
+
@default_subcommand = cmd
|
|
2200
|
+
end
|
|
2201
|
+
|
|
2202
|
+
def default_subcommand
|
|
2203
|
+
@default_subcommand
|
|
2204
|
+
end
|
|
2205
|
+
|
|
2206
|
+
def subcommands
|
|
2207
|
+
@subcommands ||= {}
|
|
2208
|
+
end
|
|
2209
|
+
|
|
2210
|
+
def visible_subcommands
|
|
2211
|
+
cmds = subcommands.clone
|
|
2212
|
+
hidden_subcommands.each do |hidden_cmd|
|
|
2213
|
+
cmds.delete(hidden_cmd.to_s.gsub('_', '-'))
|
|
2214
|
+
end
|
|
2215
|
+
cmds
|
|
2216
|
+
end
|
|
2217
|
+
|
|
2218
|
+
def has_subcommand?(cmd_name)
|
|
2219
|
+
return false if cmd_name.empty?
|
|
2220
|
+
@subcommands && @subcommands[cmd_name.to_s]
|
|
2221
|
+
end
|
|
2222
|
+
|
|
2223
|
+
def add_subcommand(cmd_name, method)
|
|
2224
|
+
@subcommands ||= {}
|
|
2225
|
+
@subcommands[cmd_name.to_s] = method
|
|
2226
|
+
end
|
|
2227
|
+
|
|
2228
|
+
def remove_subcommand(cmd_name)
|
|
2229
|
+
@subcommands ||= {}
|
|
2230
|
+
@subcommands.delete(cmd_name.to_s)
|
|
2231
|
+
end
|
|
2232
|
+
|
|
2233
|
+
# register an alias for a command
|
|
2234
|
+
def alias_subcommand(alias_cmd_name, cmd_name)
|
|
2235
|
+
add_subcommand_alias(alias_cmd_name.to_s, cmd_name.to_s.gsub('_', '-'))
|
|
2236
|
+
return
|
|
2237
|
+
end
|
|
2238
|
+
|
|
2239
|
+
def subcommand_aliases
|
|
2240
|
+
@subcommand_aliases ||= {}
|
|
2241
|
+
end
|
|
2242
|
+
|
|
2243
|
+
def add_subcommand_alias(alias_cmd_name, cmd_name)
|
|
2244
|
+
@subcommand_aliases ||= {}
|
|
2245
|
+
@subcommand_aliases[alias_cmd_name.to_s] = cmd_name
|
|
2246
|
+
end
|
|
2247
|
+
|
|
2248
|
+
def remove_subcommand_alias(alias_cmd_name)
|
|
2249
|
+
@subcommand_aliases ||= {}
|
|
2250
|
+
@subcommand_aliases.delete(alias_cmd_name.to_s)
|
|
2251
|
+
end
|
|
2252
|
+
|
|
2253
|
+
def subcommand_descriptions
|
|
2254
|
+
@subcommand_descriptions ||= {}
|
|
2255
|
+
end
|
|
2256
|
+
|
|
2257
|
+
def add_subcommand_description(cmd_name, description)
|
|
2258
|
+
@subcommand_descriptions ||= {}
|
|
2259
|
+
@subcommand_descriptions[cmd_name.to_s.gsub('_', '-')] = description
|
|
2260
|
+
end
|
|
2261
|
+
|
|
2262
|
+
def get_subcommand_description(cmd_name)
|
|
2263
|
+
desc = subcommand_descriptions[cmd_name.to_s.gsub('_', '-')]
|
|
2264
|
+
if desc
|
|
2265
|
+
return desc
|
|
2266
|
+
else
|
|
2267
|
+
cmd_method = subcommands.key(cmd_name)
|
|
2268
|
+
return cmd_method ? subcommand_descriptions[cmd_method.to_s.gsub('_', '-')] : nil
|
|
2269
|
+
end
|
|
2270
|
+
end
|
|
2271
|
+
|
|
2272
|
+
def set_subcommand_descriptions(cmd_map)
|
|
2273
|
+
cmd_map.each do |cmd_name, description|
|
|
2274
|
+
add_subcommand_description(cmd_name, description)
|
|
2275
|
+
end
|
|
2276
|
+
end
|
|
2277
|
+
|
|
2278
|
+
end
|
|
2279
|
+
end
|
|
2280
|
+
end
|
|
2281
|
+
end
|