sima-cli 2.1.19__tar.gz → 2.1.21__tar.gz
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.
- {sima_cli-2.1.19/sima_cli.egg-info → sima_cli-2.1.21}/PKG-INFO +1 -1
- {sima_cli-2.1.19 → sima_cli-2.1.21}/pyproject.toml +1 -1
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/__version__.py +1 -1
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/cli.py +23 -9
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/cloudex/client.py +54 -4
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/cloudex/commands.py +234 -15
- sima_cli-2.1.21/sima_cli/cloudex/neatops.py +254 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/discover/discover.py +73 -63
- sima_cli-2.1.21/sima_cli/discover/identity.py +70 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/discover/linuxll.py +38 -3
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/download/downloader.py +37 -2
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/metadata_installer.py +11 -5
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/models/client.py +40 -15
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/models/commands.py +24 -11
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/models/rendering.py +78 -205
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/peripherals/model.py +17 -3
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/commands.py +38 -2
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/install.py +91 -27
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/neat.py +63 -10
- sima_cli-2.1.21/sima_cli/sdk/releases.json +63 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/utils.py +630 -44
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/vscode_extensions.py +16 -1
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/host.py +3 -4
- sima_cli-2.1.21/sima_cli/update/mirror_catalog.py +11 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/netboot.py +270 -87
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/netboot_device.py +16 -4
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/overlay.py +19 -7
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/remote.py +14 -3
- sima_cli-2.1.21/sima_cli/update/reset_overlay.py +92 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/swu.py +22 -11
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/swu_artifacts.py +17 -6
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/swu_certificate.py +1 -2
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/swu_target.py +35 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/net.py +4 -3
- {sima_cli-2.1.19 → sima_cli-2.1.21/sima_cli.egg-info}/PKG-INFO +1 -1
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli.egg-info/SOURCES.txt +16 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/peripherals_fixtures.py +6 -1
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_cloudex_client.py +140 -11
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_cloudex_commands.py +25 -1
- sima_cli-2.1.21/tests/unit/test_cloudex_neatops.py +584 -0
- sima_cli-2.1.21/tests/unit/test_discovery_host_tools.py +103 -0
- sima_cli-2.1.21/tests/unit/test_download_retries.py +115 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_generate_cli_markdown_docs.py +16 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_host_update.py +11 -1
- sima_cli-2.1.21/tests/unit/test_insight_url_routing.py +55 -0
- sima_cli-2.1.21/tests/unit/test_install_script_terminal.py +153 -0
- sima_cli-2.1.21/tests/unit/test_mirror_catalog.py +61 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_models.py +261 -201
- sima_cli-2.1.21/tests/unit/test_netboot_autoflash.py +197 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_netboot_device.py +39 -0
- sima_cli-2.1.21/tests/unit/test_netboot_identity.py +331 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_netboot_ip_recovery.py +4 -3
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_overlay.py +41 -2
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_peripherals_commands.py +21 -1
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_peripherals_contract.py +11 -2
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_peripherals_model.py +22 -3
- sima_cli-2.1.21/tests/unit/test_reset_overlay.py +159 -0
- sima_cli-2.1.21/tests/unit/test_sdk_agent_sessions.py +884 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_image_detection.py +438 -46
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_model_compiler.py +47 -0
- sima_cli-2.1.21/tests/unit/test_sdk_release_mapping.py +97 -0
- sima_cli-2.1.21/tests/unit/test_selectable_resources.py +98 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_swu.py +55 -7
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_swu_certificate.py +4 -3
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_swu_mirror.py +13 -2
- sima_cli-2.1.21/tests/unit/test_swu_target.py +158 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_vscode_extension_pins.py +5 -3
- sima_cli-2.1.19/tests/unit/test_netboot_autoflash.py +0 -85
- sima_cli-2.1.19/tests/unit/test_swu_target.py +0 -58
- {sima_cli-2.1.19 → sima_cli-2.1.21}/LICENSE +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/MANIFEST.in +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/README.md +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/requirements.txt +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/setup.cfg +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/setup.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/__main__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/app_zoo/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/app_zoo/app.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/app_zoo/commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/auth/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/auth/auth0.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/auth/devportal.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/auth/login.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/cloudex/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/cloudex/installer.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/data/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/data/resources_internal.yaml +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/data/resources_public.yaml +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/deploy_only/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/deploy_only/device/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/deploy_only/device/commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/deploy_only/mpk/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/deploy_only/mpk/commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/discover/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/download/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/download/parallel.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/compatibility.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/github_assets.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/hostdriver.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/metadata_info.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/metadata_validator.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/optiview.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/package_builder.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/palette.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/install/registry.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/mla/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/mla/meminfo.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/model_zoo/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/model_zoo/model.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/models/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/network/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/network/network.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/peripherals/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/peripherals/commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/peripherals/sentinel.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/playbooks/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/playbooks/commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/playbooks/manager.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/browser_ca.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/cmdexec.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/config.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/container_registry.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/docker_staging.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/linux_devkit_network.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/linux_shared_network.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/model_compiler.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/network_doctor.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/preinstall.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/requirements.json +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/script.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/stop.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/sdk/uninstall.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/serial/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/serial/serial.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/storage/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/storage/nvme.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/storage/sdcard.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/ab_state.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/bmaptool.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/bootimg.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/cleanlog.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/elxr.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/emmc.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/fast_upload.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/local.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/netboot_artifacts.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/query.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/rootfs.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/staging.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/swap.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/uboot_environment.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/update/updater.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/upgrade/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/upgrade/selfupdate.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/api_common.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/artifactory.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/common.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/config.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/config_loader.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/container_registries.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/deprecation.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/device_api.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/disk.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/docker.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/env.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/errors.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/mpk_api.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/network.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/pcie.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/pkg_update_check.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/serializers.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/services.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/streaming.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/utils/tag.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/vulcan/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/vulcan/artifacts.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli/vulcan/commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli.egg-info/dependency_links.txt +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli.egg-info/entry_points.txt +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli.egg-info/requires.txt +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/sima_cli.egg-info/top_level.txt +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/e2e/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/__init__.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_ab_overlay.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_app_zoo.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_appzoo_commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_artifact_parallel_integration.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_auth.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_auth0.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_bootimg_defaults.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_bootimg_recovery.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_cli.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_cli_stdio.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_cli_update.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_cli_update_rerun.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_cloudex_installer.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_code_ui_readiness.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_container_registries.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_deprecation.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_device_discovery_cloudex.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_docker_utils.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_download.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_elxr_update.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_emmc.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_fast_upload.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_firmware.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_firmware_creation_order.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_firmware_layout.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_firmware_selector.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_install_force.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_install_stub.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_localized_docs_links.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_metadata_installer.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_metadata_validator.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_mla_meminfo.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_model_deployments.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_model_zoo.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_modelzoo_commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_netboot.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_netboot_images.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_netboot_mirror.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_netboot_trootctl.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_network_custom_static.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_network_safety.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_package_builder.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_palette_image_discovery.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_parallel_downloads.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_pcie.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_peripherals_sentinel.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_pkg_update_check.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_browser_ca.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_container_registry.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_host_key_trust.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_linux_shared_network.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_network_doctor.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_preinstall.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_sdk_uninstall.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_selfupdate.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_skills_commands.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_skills_manager.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_slot_swap.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_streaming_downloads.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_swu_errors.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_uboot_environment.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_update_staging.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_utils.py +0 -0
- {sima_cli-2.1.19 → sima_cli-2.1.21}/tests/unit/test_vulcan.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# sima_cli/__version__.py
|
|
2
|
-
__version__ = "2.1.
|
|
2
|
+
__version__ = "2.1.21"
|
|
@@ -180,14 +180,14 @@ class InspectionAwareGroup(click.Group):
|
|
|
180
180
|
_command_name_from_argv(['sima-cli'] + options) == 'bootimg'
|
|
181
181
|
and any(arg in ('--netboot', '-n', '--autoflash', '-a') for arg in options)
|
|
182
182
|
)
|
|
183
|
-
if _command_name_from_argv(['sima-cli'] + options) == 'update' and any(arg in ('--inspect', '--swap') for arg in options):
|
|
183
|
+
if _command_name_from_argv(['sima-cli'] + options) == 'update' and any(arg in ('--inspect', '--swap', '--reset-overlay') for arg in options):
|
|
184
184
|
ctx.meta['update_inspect'] = True
|
|
185
185
|
return super().parse_args(ctx, args)
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
# Entry point for the CLI tool using Click's command group decorator
|
|
189
189
|
@click.group(cls=InspectionAwareGroup, context_settings=dict(help_option_names=["-h", "--help", "-?"], max_content_width=120))
|
|
190
|
-
@click.option('-i', '--internal', is_flag=True, help="Use internal
|
|
190
|
+
@click.option('-i', '--internal', is_flag=True, help="Use internal SiMa services, authorized SiMa employees only")
|
|
191
191
|
@click.option('-y', '--yes', is_flag=True, help="Assume yes for confirmation prompts.")
|
|
192
192
|
@click.version_option(version=f"{__version__}", message="SiMa CLI version: %(version)s")
|
|
193
193
|
@click.pass_context
|
|
@@ -196,7 +196,7 @@ def main(ctx, internal, yes):
|
|
|
196
196
|
sima-cli – SiMa Developer Portal CLI Tool
|
|
197
197
|
|
|
198
198
|
Global Options:
|
|
199
|
-
--internal Use internal
|
|
199
|
+
--internal Use internal SiMa services (can also be set via env variable SIMA_CLI_INTERNAL=1)
|
|
200
200
|
"""
|
|
201
201
|
internal = internal or os.getenv("SIMA_CLI_INTERNAL", "0") in ("1", "true", "yes")
|
|
202
202
|
if internal:
|
|
@@ -204,7 +204,10 @@ def main(ctx, internal, yes):
|
|
|
204
204
|
Text("Pre-release software may be unstable. Use at your own risk.", style="yellow"),
|
|
205
205
|
title="Pre-release software", border_style="yellow", width=72,
|
|
206
206
|
))
|
|
207
|
-
|
|
207
|
+
# CloudEx selects Developer Portal or internal NeatOps authentication itself;
|
|
208
|
+
# its internal Vulcan flow does not require Artifactory setup.
|
|
209
|
+
if ctx.meta.get('update_inspect') or ctx.invoked_subcommand == 'cloudex':
|
|
210
|
+
_configure_stdio_errors()
|
|
208
211
|
ctx.ensure_object(dict)
|
|
209
212
|
ctx.obj.update(internal=internal, yes=yes)
|
|
210
213
|
return
|
|
@@ -363,15 +366,16 @@ def download(ctx, url, dest):
|
|
|
363
366
|
"--dryrun",
|
|
364
367
|
is_flag=True,
|
|
365
368
|
default=False,
|
|
366
|
-
help="For eLxr, preview an update
|
|
369
|
+
help="For eLxr, preview an update, slot swap, or overlay reset without making changes."
|
|
367
370
|
)
|
|
368
371
|
@click.option("--inspect", "inspect_state", is_flag=True, help="Show eLxr 3.0+ A/B slots and overlay customizations without updating (local or --ip).")
|
|
372
|
+
@click.option("--reset-overlay", is_flag=True, help="Discard local overlay software and settings without updating firmware; reboot to use the clean system (eLxr 3.0+).")
|
|
369
373
|
@click.option("--swap", is_flag=True, help="Select the available inactive eLxr 3.0+ slot for next boot; use --reboot to restart now.")
|
|
370
374
|
@click.option("--verbose", is_flag=True, help="Show detailed overlay file categories during eLxr 3.0+ inspection or update.")
|
|
371
375
|
@click.option("--signing-cert", metavar="URL_OR_FILE", help="SWUpdate PEM verification certificate: HTTP(S) URL or local file. Defaults to the certificate for the selected channel when configured (eLxr 3.0+).")
|
|
372
|
-
@click.option("--reboot", is_flag=True, help="Reboot after successful eLxr 3.0+ installation
|
|
376
|
+
@click.option("--reboot", is_flag=True, help="Reboot after successful eLxr 3.0+ installation, slot selection, or overlay reset.")
|
|
373
377
|
@click.pass_context
|
|
374
|
-
def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force, troot_only, dryrun, inspect_state, verbose, reboot, signing_cert, swap):
|
|
378
|
+
def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force, troot_only, dryrun, inspect_state, verbose, reboot, signing_cert, swap, reset_overlay):
|
|
375
379
|
"""
|
|
376
380
|
Update a SiMa DevKit, remote device, or Linux PCIe host.
|
|
377
381
|
|
|
@@ -383,6 +387,10 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
383
387
|
eLxr 3.0+ uses signed full-system SWU bundles. Use --inspect for A/B
|
|
384
388
|
state, --ip for remote updates, and --reboot to reboot after installation.
|
|
385
389
|
|
|
390
|
+
Use --reset-overlay to discard local software and settings without updating
|
|
391
|
+
firmware or switching slots. Save needed files first; the saved inventory
|
|
392
|
+
contains metadata only. Reboot promptly, or add --reboot. Use --dryrun to preview.
|
|
393
|
+
|
|
386
394
|
Use --swap to select the valid inactive slot without downloading an update.
|
|
387
395
|
The current and target versions are shown before confirmation; --yes skips
|
|
388
396
|
confirmation. Reboot separately to apply, or add --reboot to restart now.
|
|
@@ -393,7 +401,8 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
393
401
|
|
|
394
402
|
The verification certificate is downloaded for the selected channel when configured; use
|
|
395
403
|
--signing-cert URL_OR_FILE for an image signed with your own certificate.
|
|
396
|
-
|
|
404
|
+
For official eLxr 3.0+ releases, specify -v 3.0.0 (or -v 3.0) to
|
|
405
|
+
download the SWU from docs.sima.ai using your developer-portal login.
|
|
397
406
|
|
|
398
407
|
Internal mode warns that pre-release software may be unstable and is used
|
|
399
408
|
at your own risk. On eLxr 3.0+, unavailable Artifactory access falls back
|
|
@@ -501,6 +510,7 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
501
510
|
or troot_only
|
|
502
511
|
or inspect_state
|
|
503
512
|
or swap
|
|
513
|
+
or reset_overlay
|
|
504
514
|
or verbose
|
|
505
515
|
or reboot
|
|
506
516
|
or force
|
|
@@ -518,6 +528,8 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
518
528
|
return
|
|
519
529
|
|
|
520
530
|
is_elxr = is_devkit_running_elxr()
|
|
531
|
+
if reset_overlay and (inspect_state or swap or version_or_url or force or troot_only or signing_cert is not None or flavor != "auto"):
|
|
532
|
+
raise click.UsageError("--reset-overlay cannot be combined with --inspect, --swap, or installation options.")
|
|
521
533
|
if swap and (inspect_state or version_or_url or force or troot_only or signing_cert is not None or flavor != "auto"):
|
|
522
534
|
raise click.UsageError("--swap cannot be combined with --inspect or installation options.")
|
|
523
535
|
if inspect_state and (version_or_url or dryrun or force or troot_only or reboot or signing_cert is not None or flavor != 'auto'):
|
|
@@ -527,7 +539,7 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
527
539
|
internal=ctx.obj.get("internal", False),
|
|
528
540
|
auto_confirm=yes or ctx.obj.get("yes", False), dryrun=dryrun,
|
|
529
541
|
signing_cert=signing_cert, reboot=reboot, inspect=inspect_state, force=force,
|
|
530
|
-
troot_only=troot_only, flavor=flavor, local_elxr=is_elxr, verbose=verbose, swap=swap):
|
|
542
|
+
troot_only=troot_only, flavor=flavor, local_elxr=is_elxr, verbose=verbose, swap=swap, reset_overlay=reset_overlay):
|
|
531
543
|
return
|
|
532
544
|
except (click.ClickException, click.Abort):
|
|
533
545
|
raise
|
|
@@ -815,6 +827,8 @@ def bootimg_cmd(ctx, version, boardtype, netboot, images, delete_cache, devkit_i
|
|
|
815
827
|
else:
|
|
816
828
|
write_image(version, boardtype, fwtype, internal, flavor='headless', recovery=recovery)
|
|
817
829
|
click.echo("✅ Boot image successfully written.")
|
|
830
|
+
except click.ClickException:
|
|
831
|
+
raise
|
|
818
832
|
except Exception as e:
|
|
819
833
|
click.echo(f"❌ Failed to write boot image: {e}", err=True)
|
|
820
834
|
ctx.exit(1)
|
|
@@ -7,6 +7,7 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import shutil
|
|
9
9
|
import subprocess
|
|
10
|
+
import sys
|
|
10
11
|
import time
|
|
11
12
|
import uuid
|
|
12
13
|
from pathlib import Path
|
|
@@ -17,6 +18,8 @@ import requests
|
|
|
17
18
|
|
|
18
19
|
DEFAULT_ROOT = Path.home() / ".sima-cli" / "cloudex"
|
|
19
20
|
FORWARDER_NAME = "kerrigan-p2p-forwarder"
|
|
21
|
+
MACOS_TUNNEL_PATH = "/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin:/usr/local/bin"
|
|
22
|
+
MACOS_WIREGUARD_INSTALL = "Install WireGuard CLI support with: brew install wireguard-tools"
|
|
20
23
|
CONNECTION_SETUP_TIMEOUT_SECONDS = 60
|
|
21
24
|
DIAGNOSTIC_LOG_LINES = 50
|
|
22
25
|
SAFE_FORWARDER_STAGES = {
|
|
@@ -68,8 +71,7 @@ def decode_key(value):
|
|
|
68
71
|
raise CloudExError("--key contains an invalid value")
|
|
69
72
|
if len(profile["allocation_secret"]) < 32:
|
|
70
73
|
raise CloudExError("--key contains an invalid allocation secret")
|
|
71
|
-
|
|
72
|
-
raise CloudExError("CloudEx currently supports only vulcan-staging allocation keys")
|
|
74
|
+
# The environment is a descriptive label; api_url selects the backend.
|
|
73
75
|
return profile
|
|
74
76
|
|
|
75
77
|
|
|
@@ -393,6 +395,9 @@ def _forwarder_failure(log_path):
|
|
|
393
395
|
"start wireguard-go": "the WireGuard interface could not start",
|
|
394
396
|
"invalid session config": "the native forwarder rejected the session configuration",
|
|
395
397
|
}
|
|
398
|
+
if sys.platform == "darwin":
|
|
399
|
+
for needle in ("wireguard is unavailable", "start wireguard-go"):
|
|
400
|
+
mappings[needle] += ". " + MACOS_WIREGUARD_INSTALL
|
|
396
401
|
for needle, message in mappings.items():
|
|
397
402
|
if needle in detail:
|
|
398
403
|
return message + suffix
|
|
@@ -552,6 +557,50 @@ def _reconcile_existing_connection(profile, store, progress):
|
|
|
552
557
|
return None
|
|
553
558
|
|
|
554
559
|
|
|
560
|
+
def _forwarder_sudo_prefix(progress=None):
|
|
561
|
+
"""Install missing macOS tools as the user, then expose them through sudo."""
|
|
562
|
+
prefix = ["sudo", "-n", "--"]
|
|
563
|
+
if sys.platform != "darwin":
|
|
564
|
+
return prefix
|
|
565
|
+
missing = [name for name in ("wg", "wireguard-go")
|
|
566
|
+
if shutil.which(name, path=MACOS_TUNNEL_PATH) is None]
|
|
567
|
+
if missing:
|
|
568
|
+
brew = shutil.which("brew", path=MACOS_TUNNEL_PATH)
|
|
569
|
+
if brew is None:
|
|
570
|
+
raise CloudExError(
|
|
571
|
+
"CloudEx requires {} on macOS. Install Homebrew, then retry CloudEx; "
|
|
572
|
+
"missing WireGuard dependencies will be installed automatically.".format(
|
|
573
|
+
", ".join(missing)
|
|
574
|
+
)
|
|
575
|
+
)
|
|
576
|
+
packages = ["wireguard-tools" if name == "wg" else name for name in missing]
|
|
577
|
+
if progress:
|
|
578
|
+
progress.update("Installing missing WireGuard dependencies with Homebrew")
|
|
579
|
+
environment = dict(os.environ, PATH=MACOS_TUNNEL_PATH)
|
|
580
|
+
try:
|
|
581
|
+
result = subprocess.run(
|
|
582
|
+
[brew, "install", *packages],
|
|
583
|
+
env=environment, stdin=subprocess.DEVNULL,
|
|
584
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=600,
|
|
585
|
+
)
|
|
586
|
+
except (OSError, subprocess.TimeoutExpired) as exc:
|
|
587
|
+
raise CloudExError(
|
|
588
|
+
"Could not install WireGuard dependencies with Homebrew. " + MACOS_WIREGUARD_INSTALL
|
|
589
|
+
) from exc
|
|
590
|
+
if result.returncode:
|
|
591
|
+
detail = (result.stderr or result.stdout or "Homebrew failed").strip()
|
|
592
|
+
raise CloudExError("WireGuard dependency installation failed: " + detail[-1024:])
|
|
593
|
+
missing = [name for name in ("wg", "wireguard-go")
|
|
594
|
+
if shutil.which(name, path=MACOS_TUNNEL_PATH) is None]
|
|
595
|
+
if missing:
|
|
596
|
+
raise CloudExError(
|
|
597
|
+
"Homebrew completed but CloudEx still cannot find: {}. {}".format(
|
|
598
|
+
", ".join(missing), MACOS_WIREGUARD_INSTALL
|
|
599
|
+
)
|
|
600
|
+
)
|
|
601
|
+
return prefix + ["/usr/bin/env", "PATH=" + MACOS_TUNNEL_PATH]
|
|
602
|
+
|
|
603
|
+
|
|
555
604
|
def connect(profile, store, progress, attempts=3, transport="auto"):
|
|
556
605
|
"""Create a session and return its durable local record."""
|
|
557
606
|
from .installer import ensure_forwarder
|
|
@@ -561,6 +610,7 @@ def connect(profile, store, progress, attempts=3, transport="auto"):
|
|
|
561
610
|
existing = _reconcile_existing_connection(profile, store, progress)
|
|
562
611
|
if existing:
|
|
563
612
|
return existing
|
|
613
|
+
sudo_prefix = _forwarder_sudo_prefix(progress=progress)
|
|
564
614
|
forwarder = ensure_forwarder(progress=progress)
|
|
565
615
|
choices = (["p2p"] * attempts + ["routed"] if transport == "auto"
|
|
566
616
|
else ["routed"] if transport == "turn" else ["p2p"] * attempts)
|
|
@@ -610,8 +660,8 @@ def connect(profile, store, progress, attempts=3, transport="auto"):
|
|
|
610
660
|
config_path.chmod(0o600)
|
|
611
661
|
with log_path.open("x", encoding="utf-8") as log:
|
|
612
662
|
process = subprocess.Popen(
|
|
613
|
-
[
|
|
614
|
-
|
|
663
|
+
sudo_prefix + [
|
|
664
|
+
str(forwarder), "session", "--role", "user",
|
|
615
665
|
"--config", str(config_path), "--report", str(report_path),
|
|
616
666
|
],
|
|
617
667
|
stdin=subprocess.DEVNULL,
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"""Hidden, experimental CloudEx command group."""
|
|
2
2
|
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
3
5
|
import os
|
|
6
|
+
import uuid
|
|
4
7
|
import stat
|
|
5
8
|
import sys
|
|
6
9
|
import time
|
|
7
10
|
from datetime import datetime, timezone
|
|
11
|
+
from functools import wraps
|
|
8
12
|
from pathlib import Path
|
|
9
13
|
|
|
10
14
|
import click
|
|
@@ -25,6 +29,7 @@ from .client import (
|
|
|
25
29
|
run_benchmark,
|
|
26
30
|
)
|
|
27
31
|
from .installer import DEFAULT_BRANCH, install_forwarder
|
|
32
|
+
from .neatops import NeatOps
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
console = Console()
|
|
@@ -206,9 +211,33 @@ def _session_table(rows, show_benchmark=False):
|
|
|
206
211
|
return table
|
|
207
212
|
|
|
208
213
|
|
|
214
|
+
def _internal_cloudex():
|
|
215
|
+
ctx = click.get_current_context(silent=True)
|
|
216
|
+
return bool(ctx and isinstance(ctx.obj, dict) and ctx.obj.get("internal"))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _require_vulcan():
|
|
220
|
+
if not _internal_cloudex():
|
|
221
|
+
raise click.ClickException(
|
|
222
|
+
"CloudEx is not available on Arthas yet. Use sima-cli -i cloudex for Vulcan."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _vulcan_only(function):
|
|
227
|
+
@wraps(function)
|
|
228
|
+
def wrapped(*args, **kwargs):
|
|
229
|
+
_require_vulcan()
|
|
230
|
+
return function(*args, **kwargs)
|
|
231
|
+
return wrapped
|
|
232
|
+
|
|
233
|
+
|
|
209
234
|
@click.group(name="cloudex", hidden=True)
|
|
210
|
-
|
|
211
|
-
|
|
235
|
+
@click.option("-i", "--internal", is_flag=True, help="Use internal Vulcan CloudEx and NeatOps authentication.")
|
|
236
|
+
@click.pass_context
|
|
237
|
+
def cloudex_group(ctx, internal):
|
|
238
|
+
"""Manage CloudEx devices; use -i for internal Vulcan access."""
|
|
239
|
+
ctx.ensure_object(dict)
|
|
240
|
+
ctx.obj["internal"] = internal or ctx.obj.get("internal", False) or os.getenv("SIMA_CLI_INTERNAL", "0") in ("1", "true", "yes")
|
|
212
241
|
|
|
213
242
|
|
|
214
243
|
@cloudex_group.command("connect")
|
|
@@ -222,6 +251,8 @@ def cloudex_group():
|
|
|
222
251
|
is_flag=True,
|
|
223
252
|
help="Read the allocation key from standard input for automation.",
|
|
224
253
|
)
|
|
254
|
+
@click.option("--allocation", help="Connect to your NeatOps allocation ID without copying a key.")
|
|
255
|
+
@click.option("--environment", type=click.Choice(["production", "staging"]), default="production", show_default=True, help="Vulcan environment (requires -i).")
|
|
225
256
|
@click.option("--transport", type=click.Choice(["auto", "p2p", "turn"]), default="auto", hidden=True)
|
|
226
257
|
@click.option("--attempts", type=click.IntRange(1, 5), default=3, hidden=True)
|
|
227
258
|
@click.option(
|
|
@@ -229,11 +260,16 @@ def cloudex_group():
|
|
|
229
260
|
is_flag=True,
|
|
230
261
|
help="Print each sanitized connection stage for troubleshooting.",
|
|
231
262
|
)
|
|
232
|
-
|
|
263
|
+
@_vulcan_only
|
|
264
|
+
def connect_command(key_file, key_stdin, allocation, environment, transport, attempts, verbose):
|
|
233
265
|
"""Connect to an allocated DevKit using a securely supplied key."""
|
|
234
|
-
if key_file
|
|
235
|
-
raise click.UsageError("Use either --key-file or --key-stdin, not both.")
|
|
266
|
+
if sum(bool(value) for value in (key_file, key_stdin, allocation)) > 1:
|
|
267
|
+
raise click.UsageError("Use either --key-file or --key-stdin, not both; --allocation must be used alone.")
|
|
236
268
|
try:
|
|
269
|
+
if allocation:
|
|
270
|
+
profile = _allocation_profile(_neatops_api(environment), _allocation_id(allocation))
|
|
271
|
+
_connect_profile(profile, verbose=verbose, attempts=attempts, transport=transport)
|
|
272
|
+
return
|
|
237
273
|
if key_file:
|
|
238
274
|
key = _read_key_file(key_file)
|
|
239
275
|
elif key_stdin:
|
|
@@ -243,16 +279,7 @@ def connect_command(key_file, key_stdin, transport, attempts, verbose):
|
|
|
243
279
|
else:
|
|
244
280
|
key = click.prompt("CloudEx allocation key", hide_input=True).strip()
|
|
245
281
|
profile = decode_key(key)
|
|
246
|
-
|
|
247
|
-
_authorize_supported_host()
|
|
248
|
-
with StageProgress("Preparing CloudEx connection", verbose=verbose) as progress:
|
|
249
|
-
session = connect(profile, _store(), progress, attempts=attempts, transport=transport)
|
|
250
|
-
progress.success("Connected to {}".format(session["device"]))
|
|
251
|
-
path = session.get("ice_path", "unknown")
|
|
252
|
-
path_text = "direct" if path == "direct" else "relayed" if path == "relay" else path
|
|
253
|
-
console.print(" [dim]Session[/dim] {}".format(_short(session["session_id"])))
|
|
254
|
-
console.print(" [dim]Path[/dim] {}".format(path_text))
|
|
255
|
-
console.print(" [dim]SSH[/dim] [bold]ssh sima@{}[/bold]".format(session["target"]))
|
|
282
|
+
_connect_profile(profile, verbose=verbose, attempts=attempts, transport=transport)
|
|
256
283
|
except CloudExError as exc:
|
|
257
284
|
raise click.ClickException(str(exc)) from exc
|
|
258
285
|
|
|
@@ -264,6 +291,7 @@ def connect_command(key_file, key_stdin, transport, attempts, verbose):
|
|
|
264
291
|
show_default=True,
|
|
265
292
|
help="Kerrigan branch whose latest forwarder should be installed.",
|
|
266
293
|
)
|
|
294
|
+
@_vulcan_only
|
|
267
295
|
def update_command(branch):
|
|
268
296
|
"""Install or update the native CloudEx forwarder."""
|
|
269
297
|
try:
|
|
@@ -326,6 +354,7 @@ def _choose_sessions(sessions):
|
|
|
326
354
|
@cloudex_group.command("disconnect")
|
|
327
355
|
@click.option("--session", "session_id", help="Session ID or unique prefix to disconnect.")
|
|
328
356
|
@click.option("--all", "disconnect_all", is_flag=True, help="Disconnect every recorded CloudEx session.")
|
|
357
|
+
@_vulcan_only
|
|
329
358
|
def disconnect_command(session_id, disconnect_all):
|
|
330
359
|
"""Disconnect one or all CloudEx sessions."""
|
|
331
360
|
if session_id and disconnect_all:
|
|
@@ -373,6 +402,7 @@ def disconnect_command(session_id, disconnect_all):
|
|
|
373
402
|
@click.option("--benchmark", is_flag=True, help="Stress-test RTT and maximum bidirectional throughput for each connection.")
|
|
374
403
|
@click.option("--duration", type=click.IntRange(1, 60), default=10, show_default=True,
|
|
375
404
|
help="Throughput measurement window in seconds.")
|
|
405
|
+
@_vulcan_only
|
|
376
406
|
def list_command(benchmark, duration):
|
|
377
407
|
"""List locally recorded CloudEx connections."""
|
|
378
408
|
store = _store()
|
|
@@ -420,3 +450,192 @@ def list_command(benchmark, duration):
|
|
|
420
450
|
|
|
421
451
|
def register_cloudex_commands(main):
|
|
422
452
|
main.add_command(cloudex_group)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _connect_profile(profile, verbose=False, attempts=3, transport="auto"):
|
|
456
|
+
console.print("[cyan]→[/cyan] Authorizing encrypted tunnel access")
|
|
457
|
+
_authorize_supported_host()
|
|
458
|
+
with StageProgress("Preparing CloudEx connection", verbose=verbose) as progress:
|
|
459
|
+
session = connect(profile, _store(), progress, attempts=attempts, transport=transport)
|
|
460
|
+
progress.success("Connected to {}".format(session["device"]))
|
|
461
|
+
path = session.get("ice_path", "unknown")
|
|
462
|
+
path_text = "direct" if path == "direct" else "relayed" if path == "relay" else path
|
|
463
|
+
console.print(" [dim]Session[/dim] {}".format(_short(session["session_id"])))
|
|
464
|
+
console.print(" [dim]Path[/dim] {}".format(path_text))
|
|
465
|
+
console.print(" [dim]SSH[/dim] [bold]ssh sima@{}[/bold]".format(session["target"]))
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
DURATIONS = {"1h": 3600, "4h": 14400, "8h": 28800, "1d": 86400, "7d": 604800, "4w": 2419200}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _allocation_id(value):
|
|
472
|
+
if len(value) != 32 or any(c not in "0123456789abcdef" for c in value):
|
|
473
|
+
raise CloudExError("Use the full 32-character allocation ID from sima-cli -i cloudex devices")
|
|
474
|
+
return value
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _allocation_profile(api, allocation_id, timeout=180):
|
|
478
|
+
deadline = time.monotonic() + timeout
|
|
479
|
+
with StageProgress("Waiting for device preparation") as progress:
|
|
480
|
+
while True:
|
|
481
|
+
inventory = api.call("GET", "/devkits")
|
|
482
|
+
allocation = next((a for a in inventory.get("allocations", []) if a["session_id"] == allocation_id), None)
|
|
483
|
+
if not allocation or allocation.get("status") in {"released", "releasing", "expired", "failed"}:
|
|
484
|
+
raise CloudExError("Allocation is unavailable; check sima-cli -i cloudex devices")
|
|
485
|
+
if allocation.get("status") in {"ready", "active"}:
|
|
486
|
+
profile = api.call("POST", "/devkits/allocations/" + allocation_id + "/profile", {})
|
|
487
|
+
# Reuse the same strict profile validation as manually supplied keys.
|
|
488
|
+
profile = decode_key(base64.b64encode(json.dumps(profile).encode()).decode())
|
|
489
|
+
progress.success("Device is ready")
|
|
490
|
+
return profile
|
|
491
|
+
if time.monotonic() >= deadline:
|
|
492
|
+
raise CloudExError("Device preparation timed out. Allocation {} is retained; retry with sima-cli -i cloudex connect --allocation {} or release it".format(allocation_id, allocation_id))
|
|
493
|
+
time.sleep(2)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _neatops_login(api):
|
|
497
|
+
api.login(lambda url: console.print("Open this URL in a browser on this computer:\n" + url, markup=False, soft_wrap=True))
|
|
498
|
+
console.print("[green]Signed in to NeatOps ({})[/green]".format(api.environment))
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _neatops_api(environment):
|
|
502
|
+
_require_vulcan()
|
|
503
|
+
return NeatOps(environment, on_login_required=_neatops_login)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
@cloudex_group.command("login")
|
|
507
|
+
@click.option("--environment", type=click.Choice(["production", "staging"]), default="production", show_default=True, help="Vulcan environment (requires -i).")
|
|
508
|
+
def login_command(environment):
|
|
509
|
+
"""Sign in to the Developer Portal, or NeatOps with -i."""
|
|
510
|
+
if not _internal_cloudex():
|
|
511
|
+
if environment != "production":
|
|
512
|
+
raise click.UsageError("--environment selects a Vulcan environment and requires -i.")
|
|
513
|
+
from sima_cli.auth.devportal import login_external
|
|
514
|
+
if not login_external(loginDocker=False):
|
|
515
|
+
raise click.ClickException("Developer Portal sign-in was not completed.")
|
|
516
|
+
console.print("Signed in to the Developer Portal. CloudEx is not available on Arthas yet.")
|
|
517
|
+
return
|
|
518
|
+
try:
|
|
519
|
+
_neatops_login(NeatOps(environment))
|
|
520
|
+
except CloudExError as exc:
|
|
521
|
+
raise click.ClickException(str(exc)) from exc
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
@cloudex_group.command("logout")
|
|
525
|
+
@click.option("--environment", type=click.Choice(["production", "staging"]), default="production", show_default=True, help="Vulcan environment (requires -i).")
|
|
526
|
+
def logout_command(environment):
|
|
527
|
+
"""Sign out of the Developer Portal, or NeatOps with -i; keep leases."""
|
|
528
|
+
if not _internal_cloudex():
|
|
529
|
+
if environment != "production":
|
|
530
|
+
raise click.UsageError("--environment selects a Vulcan environment and requires -i.")
|
|
531
|
+
from sima_cli.auth.auth0 import clear_external_login_state
|
|
532
|
+
clear_external_login_state()
|
|
533
|
+
console.print("Signed out of the Developer Portal.")
|
|
534
|
+
return
|
|
535
|
+
try:
|
|
536
|
+
NeatOps(environment).logout()
|
|
537
|
+
console.print("Signed out of NeatOps. Existing device leases are unchanged.")
|
|
538
|
+
except CloudExError as exc:
|
|
539
|
+
raise click.ClickException(str(exc)) from exc
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
@cloudex_group.command("devices")
|
|
543
|
+
@click.option("--environment", type=click.Choice(["production", "staging"]), default="production", show_default=True, help="Vulcan environment (requires -i).")
|
|
544
|
+
@_vulcan_only
|
|
545
|
+
def devices_command(environment):
|
|
546
|
+
"""Show NeatOps device availability and your allocation IDs."""
|
|
547
|
+
try:
|
|
548
|
+
inventory = _neatops_api(environment).call("GET", "/devkits")
|
|
549
|
+
table = Table("Device type", "Firmware version", "ID", "Allocation ID", "State")
|
|
550
|
+
allocations = {(a["cluster_id"], a["target_id"]): a for a in inventory.get("allocations", [])}
|
|
551
|
+
visible_devices = set()
|
|
552
|
+
for d in inventory.get("devices", []):
|
|
553
|
+
device_key = (d["cluster_id"], d["id"])
|
|
554
|
+
visible_devices.add(device_key)
|
|
555
|
+
a = d.get("allocation") or allocations.get(device_key, {})
|
|
556
|
+
table.add_row(d.get("type") or "Unknown", d.get("firmware") or "Unknown", d["id"],
|
|
557
|
+
a.get("session_id") or "—", "Available" if d["available"] else "Not available")
|
|
558
|
+
# Allocations can survive an appliance disappearing from inventory.
|
|
559
|
+
for a in inventory.get("allocations", []):
|
|
560
|
+
if (a["cluster_id"], a["target_id"]) not in visible_devices:
|
|
561
|
+
table.add_row("Unknown", "Unknown", a["target_id"], a["session_id"], "Not available")
|
|
562
|
+
console.print(table)
|
|
563
|
+
except CloudExError as exc:
|
|
564
|
+
raise click.ClickException(str(exc)) from exc
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
@cloudex_group.command("request")
|
|
568
|
+
@click.option("--device", help="Device ID or exact name; otherwise choose interactively.")
|
|
569
|
+
@click.option("--cluster", help="Appliance ID, to disambiguate devices.")
|
|
570
|
+
@click.option("--duration", type=click.Choice(list(DURATIONS)), default="1h", show_default=True)
|
|
571
|
+
@click.option("--environment", type=click.Choice(["production", "staging"]), default="production", show_default=True, help="Vulcan environment (requires -i).")
|
|
572
|
+
@click.option("--verbose", is_flag=True, help="Print sanitized connection stages.")
|
|
573
|
+
@_vulcan_only
|
|
574
|
+
def request_command(device, cluster, duration, environment, verbose):
|
|
575
|
+
"""Request a NeatOps device, wait for preparation, and connect automatically."""
|
|
576
|
+
allocation_id = None
|
|
577
|
+
try:
|
|
578
|
+
api = _neatops_api(environment)
|
|
579
|
+
entitlement = api.call("GET", "/users/me/entitlement")["entitlement"]
|
|
580
|
+
if DURATIONS[duration] > entitlement["max_lease_duration_seconds"]:
|
|
581
|
+
raise CloudExError("Requested duration exceeds your NeatOps lease limit; choose a shorter duration")
|
|
582
|
+
inventory = api.call("GET", "/devkits")
|
|
583
|
+
if len(inventory.get("allocations", [])) >= entitlement["max_active_leases"]:
|
|
584
|
+
raise CloudExError("Your device lease limit is reached; connect to an existing allocation or release one")
|
|
585
|
+
candidates = [d for d in inventory.get("devices", []) if d.get("available")
|
|
586
|
+
and (not cluster or d["cluster_id"] == cluster)
|
|
587
|
+
and (not device or device in {d["id"], d["name"]})]
|
|
588
|
+
if not candidates:
|
|
589
|
+
raise CloudExError("No matching devices are available; check sima-cli -i cloudex devices")
|
|
590
|
+
if device and len(candidates) > 1:
|
|
591
|
+
raise CloudExError("Device name or ID is ambiguous; specify --cluster")
|
|
592
|
+
if not device:
|
|
593
|
+
table = Table("#", "Device type", "Software version", "ID")
|
|
594
|
+
for index, d in enumerate(candidates, 1):
|
|
595
|
+
table.add_row(str(index), d.get("type") or "Unknown", d.get("firmware") or "Unknown", d["id"])
|
|
596
|
+
console.print(table)
|
|
597
|
+
choice = click.prompt("Choose a device", type=click.IntRange(1, len(candidates)))
|
|
598
|
+
selected = candidates[choice - 1]
|
|
599
|
+
else:
|
|
600
|
+
selected = candidates[0]
|
|
601
|
+
# Get local administrator authorization before reserving scarce hardware.
|
|
602
|
+
_authorize_supported_host()
|
|
603
|
+
allocation_id = uuid.uuid4().hex
|
|
604
|
+
console.print("Allocation {} · {} · {}".format(allocation_id, selected["name"], duration), markup=False)
|
|
605
|
+
api.call("POST", "/devkits/allocations", {"request_id": allocation_id,
|
|
606
|
+
"cluster_id": selected["cluster_id"], "target_id": selected["id"], "duration_seconds": DURATIONS[duration]})
|
|
607
|
+
profile = _allocation_profile(api, allocation_id)
|
|
608
|
+
_connect_profile(profile, verbose=verbose)
|
|
609
|
+
except CloudExError as exc:
|
|
610
|
+
if allocation_id:
|
|
611
|
+
console.print("Inspect: sima-cli -i cloudex devices --environment {}\nResume: sima-cli -i cloudex connect --allocation {} --environment {}\nRelease: sima-cli -i cloudex release {} --environment {}".format(environment, allocation_id, environment, allocation_id, environment), markup=False, soft_wrap=True)
|
|
612
|
+
raise click.ClickException(str(exc)) from exc
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
@cloudex_group.command("release")
|
|
616
|
+
@click.argument("allocation_id")
|
|
617
|
+
@click.option("--environment", type=click.Choice(["production", "staging"]), default="production", show_default=True, help="Vulcan environment (requires -i).")
|
|
618
|
+
@_vulcan_only
|
|
619
|
+
def release_command(allocation_id, environment):
|
|
620
|
+
"""Release your NeatOps lease and disconnect its local CloudEx sessions."""
|
|
621
|
+
try:
|
|
622
|
+
allocation_id = _allocation_id(allocation_id)
|
|
623
|
+
store = _store()
|
|
624
|
+
failures = []
|
|
625
|
+
for session in store.list():
|
|
626
|
+
if session.get("allocation_id") == allocation_id:
|
|
627
|
+
try:
|
|
628
|
+
with StageProgress("Disconnecting CloudEx") as progress:
|
|
629
|
+
disconnect_session(session, store, progress)
|
|
630
|
+
except CloudExError as exc:
|
|
631
|
+
failures.append("Tunnel cleanup failed: {}. Local retry state retained; retry sima-cli -i cloudex disconnect.".format(exc))
|
|
632
|
+
try:
|
|
633
|
+
_neatops_api(environment).call("DELETE", "/devkits/allocations/" + allocation_id)
|
|
634
|
+
except CloudExError as exc:
|
|
635
|
+
failures.append("Lease release failed: {}".format(exc))
|
|
636
|
+
else:
|
|
637
|
+
console.print("Lease released; device availability returns after cleanup is confirmed.")
|
|
638
|
+
if failures:
|
|
639
|
+
raise CloudExError("; ".join(failures))
|
|
640
|
+
except CloudExError as exc:
|
|
641
|
+
raise click.ClickException(str(exc)) from exc
|