sima-cli 2.1.18__tar.gz → 2.1.19__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.18/sima_cli.egg-info → sima_cli-2.1.19}/PKG-INFO +35 -2
- {sima_cli-2.1.18 → sima_cli-2.1.19}/README.md +32 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/pyproject.toml +3 -2
- {sima_cli-2.1.18 → sima_cli-2.1.19}/requirements.txt +2 -1
- {sima_cli-2.1.18 → sima_cli-2.1.19}/setup.py +2 -1
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/__version__.py +1 -1
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cli.py +40 -14
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cloudex/client.py +2 -8
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cloudex/installer.py +33 -1
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/download/downloader.py +90 -33
- sima_cli-2.1.19/sima_cli/download/parallel.py +164 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/metadata_installer.py +5 -2
- sima_cli-2.1.19/sima_cli/mla/meminfo.py +274 -0
- sima_cli-2.1.19/sima_cli/models/client.py +891 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/models/commands.py +14 -2
- sima_cli-2.1.19/sima_cli/peripherals/__init__.py +5 -0
- sima_cli-2.1.19/sima_cli/peripherals/commands.py +208 -0
- sima_cli-2.1.19/sima_cli/peripherals/model.py +426 -0
- sima_cli-2.1.19/sima_cli/peripherals/sentinel.py +145 -0
- sima_cli-2.1.19/sima_cli/sdk/browser_ca.py +53 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/commands.py +85 -5
- sima_cli-2.1.19/sima_cli/sdk/container_registry.py +385 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/install.py +134 -17
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/neat.py +119 -21
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/network_doctor.py +104 -15
- sima_cli-2.1.19/sima_cli/sdk/preinstall.py +1759 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/utils.py +452 -63
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/vscode_extensions.py +15 -13
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/emmc.py +34 -2
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/netboot.py +112 -52
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/netboot_device.py +49 -244
- sima_cli-2.1.19/sima_cli/update/swap.py +81 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/swu.py +20 -4
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/uboot_environment.py +11 -3
- sima_cli-2.1.19/sima_cli/utils/streaming.py +84 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/vulcan/artifacts.py +31 -18
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/vulcan/commands.py +1 -1
- {sima_cli-2.1.18 → sima_cli-2.1.19/sima_cli.egg-info}/PKG-INFO +35 -2
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/SOURCES.txt +25 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/requires.txt +6 -1
- sima_cli-2.1.19/tests/unit/peripherals_fixtures.py +131 -0
- sima_cli-2.1.19/tests/unit/test_artifact_parallel_integration.py +174 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cloudex_client.py +6 -6
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cloudex_installer.py +75 -0
- sima_cli-2.1.19/tests/unit/test_code_ui_readiness.py +81 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_download.py +1 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_emmc.py +78 -12
- sima_cli-2.1.19/tests/unit/test_install_force.py +39 -0
- sima_cli-2.1.19/tests/unit/test_mla_meminfo.py +159 -0
- sima_cli-2.1.19/tests/unit/test_model_deployments.py +794 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_models.py +138 -77
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot.py +5 -1
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_autoflash.py +35 -4
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_device.py +74 -212
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_trootctl.py +4 -1
- sima_cli-2.1.19/tests/unit/test_parallel_downloads.py +131 -0
- sima_cli-2.1.19/tests/unit/test_peripherals_commands.py +293 -0
- sima_cli-2.1.19/tests/unit/test_peripherals_contract.py +96 -0
- sima_cli-2.1.19/tests/unit/test_peripherals_model.py +219 -0
- sima_cli-2.1.19/tests/unit/test_peripherals_sentinel.py +127 -0
- sima_cli-2.1.19/tests/unit/test_sdk_browser_ca.py +119 -0
- sima_cli-2.1.19/tests/unit/test_sdk_container_registry.py +544 -0
- sima_cli-2.1.19/tests/unit/test_sdk_host_key_trust.py +128 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_image_detection.py +491 -11
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_network_doctor.py +145 -1
- sima_cli-2.1.19/tests/unit/test_sdk_preinstall.py +1325 -0
- sima_cli-2.1.19/tests/unit/test_slot_swap.py +145 -0
- sima_cli-2.1.19/tests/unit/test_streaming_downloads.py +296 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu.py +22 -26
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu_mirror.py +3 -2
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_uboot_environment.py +43 -4
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_update_staging.py +11 -4
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_vscode_extension_pins.py +79 -24
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_vulcan.py +3 -0
- sima_cli-2.1.18/sima_cli/mla/meminfo.py +0 -47
- sima_cli-2.1.18/sima_cli/models/client.py +0 -600
- sima_cli-2.1.18/sima_cli/sdk/preinstall.py +0 -715
- sima_cli-2.1.18/tests/unit/test_sdk_preinstall.py +0 -432
- {sima_cli-2.1.18 → sima_cli-2.1.19}/LICENSE +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/MANIFEST.in +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/setup.cfg +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/__main__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/app_zoo/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/app_zoo/app.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/app_zoo/commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/auth/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/auth/auth0.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/auth/devportal.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/auth/login.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cloudex/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/cloudex/commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/data/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/data/resources_internal.yaml +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/data/resources_public.yaml +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/device/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/device/commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/mpk/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/deploy_only/mpk/commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/discover/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/discover/discover.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/discover/linuxll.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/download/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/compatibility.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/github_assets.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/hostdriver.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/metadata_info.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/metadata_validator.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/optiview.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/package_builder.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/palette.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/install/registry.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/mla/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/model_zoo/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/model_zoo/model.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/models/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/models/rendering.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/network/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/network/network.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/playbooks/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/playbooks/commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/playbooks/manager.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/cmdexec.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/config.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/docker_staging.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/linux_devkit_network.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/linux_shared_network.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/model_compiler.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/requirements.json +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/script.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/stop.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/sdk/uninstall.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/serial/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/serial/serial.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/storage/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/storage/nvme.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/storage/sdcard.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/ab_state.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/bmaptool.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/bootimg.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/cleanlog.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/elxr.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/fast_upload.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/host.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/local.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/netboot_artifacts.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/overlay.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/query.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/remote.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/rootfs.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/staging.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/swu_artifacts.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/swu_certificate.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/swu_target.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/update/updater.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/upgrade/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/upgrade/selfupdate.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/api_common.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/artifactory.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/common.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/config.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/config_loader.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/container_registries.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/deprecation.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/device_api.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/disk.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/docker.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/env.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/errors.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/mpk_api.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/net.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/network.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/pcie.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/pkg_update_check.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/serializers.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/services.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/utils/tag.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli/vulcan/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/dependency_links.txt +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/entry_points.txt +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/sima_cli.egg-info/top_level.txt +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/e2e/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/__init__.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_ab_overlay.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_app_zoo.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_appzoo_commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_auth.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_auth0.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_bootimg_defaults.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_bootimg_recovery.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cli.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cli_stdio.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cli_update.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cli_update_rerun.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_cloudex_commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_container_registries.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_deprecation.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_device_discovery_cloudex.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_docker_utils.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_elxr_update.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_fast_upload.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_firmware.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_firmware_creation_order.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_firmware_layout.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_firmware_selector.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_generate_cli_markdown_docs.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_host_update.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_install_stub.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_localized_docs_links.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_metadata_installer.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_metadata_validator.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_model_zoo.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_modelzoo_commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_images.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_ip_recovery.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_netboot_mirror.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_network_custom_static.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_network_safety.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_overlay.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_package_builder.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_palette_image_discovery.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_pcie.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_pkg_update_check.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_linux_shared_network.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_model_compiler.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_sdk_uninstall.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_selfupdate.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_skills_commands.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_skills_manager.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu_certificate.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu_errors.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_swu_target.py +0 -0
- {sima_cli-2.1.18 → sima_cli-2.1.19}/tests/unit/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sima-cli
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.19
|
|
4
4
|
Summary: CLI tool for SiMa Developer Portal to download models, firmware, and apps.
|
|
5
5
|
Home-page: https://community.sima.ai/
|
|
6
6
|
Author: SiMa.ai
|
|
@@ -27,7 +27,8 @@ Requires-Dist: rich==14.0.0
|
|
|
27
27
|
Requires-Dist: InquirerPy==0.3.4
|
|
28
28
|
Requires-Dist: tftpy==0.8.6
|
|
29
29
|
Requires-Dist: psutil==7.0.0
|
|
30
|
-
Requires-Dist: huggingface-hub<
|
|
30
|
+
Requires-Dist: huggingface-hub<3.0,>=2.2.0; python_version >= "3.10"
|
|
31
|
+
Requires-Dist: huggingface-hub<2.0,>=0.34.0; python_version < "3.10"
|
|
31
32
|
Requires-Dist: tabulate==0.9.0
|
|
32
33
|
Dynamic: author
|
|
33
34
|
Dynamic: license-file
|
|
@@ -148,6 +149,20 @@ sima-cli --internal login
|
|
|
148
149
|
SIMA_CLI_INTERNAL=1 sima-cli install -v 2.1.2 tools/model-compiler/amd64
|
|
149
150
|
```
|
|
150
151
|
|
|
152
|
+
Full-system SWU updates prefer RAM-backed `/tmp` when available memory can
|
|
153
|
+
hold both the bundle and its extraction, plus a reserve of at least 512 MiB
|
|
154
|
+
or 10% of total RAM (whichever is larger). Otherwise they stage in `/data`,
|
|
155
|
+
with NVMe as a further fallback if `/data` lacks space. RAM staging is temporary;
|
|
156
|
+
interrupted updates retained there do not survive a reboot.
|
|
157
|
+
|
|
158
|
+
Public-mirror downloads, `models download`, and `neat artifacts` split files
|
|
159
|
+
of at least 64 MiB into four byte ranges when supported by the server. Models
|
|
160
|
+
and Neat artifacts share a limit of four active download connections across
|
|
161
|
+
files and ranges, including single-connection fallbacks. Ranges must match the
|
|
162
|
+
expected size and an object validator or the expected SHA-256 before the file is published. Models retain signed-URL refresh and
|
|
163
|
+
cancellation handling. Existing public-mirror partial downloads continue
|
|
164
|
+
sequentially; servers without range support use a single connection.
|
|
165
|
+
|
|
151
166
|
## Common Workflows
|
|
152
167
|
|
|
153
168
|
Set up SDK containers:
|
|
@@ -157,6 +172,14 @@ sima-cli sdk setup
|
|
|
157
172
|
sima-cli sdk neat
|
|
158
173
|
```
|
|
159
174
|
|
|
175
|
+
When pairing with `sdk setup --devkit`, a new DevKit host key is automatically
|
|
176
|
+
recorded in the SDK user's `~/.ssh/known_hosts`, including during unattended
|
|
177
|
+
setup or after SDK container recreation. Subsequent pairing preserves saved
|
|
178
|
+
keys and rejects a changed key at the same address and SSH port. After a device
|
|
179
|
+
reimage or key rotation, verify the replacement fingerprint before explicitly
|
|
180
|
+
updating that entry. For environments requiring verified trust before the first
|
|
181
|
+
connection, provision verified host keys in the SDK user's `known_hosts`.
|
|
182
|
+
|
|
160
183
|
Install Model Compiler from package metadata:
|
|
161
184
|
|
|
162
185
|
```bash
|
|
@@ -238,3 +261,13 @@ For command-specific prerequisites, see the generated [command reference](docs/s
|
|
|
238
261
|
## Support
|
|
239
262
|
|
|
240
263
|
For issues and feature requests, use the sima-cli GitHub repository or contact the SiMa.ai development team.
|
|
264
|
+
|
|
265
|
+
### Remote browser certificate help
|
|
266
|
+
|
|
267
|
+
SDK setup stages a verified public mkcert CA certificate alongside the SDK HTTPS
|
|
268
|
+
certificate. SDK images with browser certificate help can offer that file for
|
|
269
|
+
download on the sign-in page, with instructions for the browser computer.
|
|
270
|
+
Only the public certificate is staged; the mkcert private key stays on the host.
|
|
271
|
+
If the issuing CA cannot be verified, including a plain self-signed fallback,
|
|
272
|
+
no CA download is published. Re-running setup refreshes the staged file even
|
|
273
|
+
when the existing HTTPS certificate does not need to be reissued.
|
|
@@ -113,6 +113,20 @@ sima-cli --internal login
|
|
|
113
113
|
SIMA_CLI_INTERNAL=1 sima-cli install -v 2.1.2 tools/model-compiler/amd64
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
Full-system SWU updates prefer RAM-backed `/tmp` when available memory can
|
|
117
|
+
hold both the bundle and its extraction, plus a reserve of at least 512 MiB
|
|
118
|
+
or 10% of total RAM (whichever is larger). Otherwise they stage in `/data`,
|
|
119
|
+
with NVMe as a further fallback if `/data` lacks space. RAM staging is temporary;
|
|
120
|
+
interrupted updates retained there do not survive a reboot.
|
|
121
|
+
|
|
122
|
+
Public-mirror downloads, `models download`, and `neat artifacts` split files
|
|
123
|
+
of at least 64 MiB into four byte ranges when supported by the server. Models
|
|
124
|
+
and Neat artifacts share a limit of four active download connections across
|
|
125
|
+
files and ranges, including single-connection fallbacks. Ranges must match the
|
|
126
|
+
expected size and an object validator or the expected SHA-256 before the file is published. Models retain signed-URL refresh and
|
|
127
|
+
cancellation handling. Existing public-mirror partial downloads continue
|
|
128
|
+
sequentially; servers without range support use a single connection.
|
|
129
|
+
|
|
116
130
|
## Common Workflows
|
|
117
131
|
|
|
118
132
|
Set up SDK containers:
|
|
@@ -122,6 +136,14 @@ sima-cli sdk setup
|
|
|
122
136
|
sima-cli sdk neat
|
|
123
137
|
```
|
|
124
138
|
|
|
139
|
+
When pairing with `sdk setup --devkit`, a new DevKit host key is automatically
|
|
140
|
+
recorded in the SDK user's `~/.ssh/known_hosts`, including during unattended
|
|
141
|
+
setup or after SDK container recreation. Subsequent pairing preserves saved
|
|
142
|
+
keys and rejects a changed key at the same address and SSH port. After a device
|
|
143
|
+
reimage or key rotation, verify the replacement fingerprint before explicitly
|
|
144
|
+
updating that entry. For environments requiring verified trust before the first
|
|
145
|
+
connection, provision verified host keys in the SDK user's `known_hosts`.
|
|
146
|
+
|
|
125
147
|
Install Model Compiler from package metadata:
|
|
126
148
|
|
|
127
149
|
```bash
|
|
@@ -203,3 +225,13 @@ For command-specific prerequisites, see the generated [command reference](docs/s
|
|
|
203
225
|
## Support
|
|
204
226
|
|
|
205
227
|
For issues and feature requests, use the sima-cli GitHub repository or contact the SiMa.ai development team.
|
|
228
|
+
|
|
229
|
+
### Remote browser certificate help
|
|
230
|
+
|
|
231
|
+
SDK setup stages a verified public mkcert CA certificate alongside the SDK HTTPS
|
|
232
|
+
certificate. SDK images with browser certificate help can offer that file for
|
|
233
|
+
download on the sign-in page, with instructions for the browser computer.
|
|
234
|
+
Only the public certificate is staged; the mkcert private key stays on the host.
|
|
235
|
+
If the issuing CA cannot be verified, including a plain self-signed fallback,
|
|
236
|
+
no CA download is published. Re-running setup refreshes the staged file even
|
|
237
|
+
when the existing HTTPS certificate does not need to be reissued.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sima-cli"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.19"
|
|
8
8
|
description = "CLI tool for SiMa Developer Portal to download models, firmware, and apps."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -33,7 +33,8 @@ dependencies = [
|
|
|
33
33
|
"InquirerPy==0.3.4",
|
|
34
34
|
"tftpy==0.8.6",
|
|
35
35
|
"psutil==7.0.0",
|
|
36
|
-
"huggingface-hub>=
|
|
36
|
+
"huggingface-hub>=2.2.0,<3.0; python_version >= '3.10'",
|
|
37
|
+
"huggingface-hub>=0.34.0,<2.0; python_version < '3.10'",
|
|
37
38
|
"tabulate==0.9.0"
|
|
38
39
|
]
|
|
39
40
|
|
|
@@ -23,7 +23,8 @@ setup(
|
|
|
23
23
|
"InquirerPy>=0.3.4,<0.4",
|
|
24
24
|
"tftpy>=0.8.6,<0.9",
|
|
25
25
|
"psutil>=7.0.0,<8.0",
|
|
26
|
-
"huggingface-hub>=
|
|
26
|
+
"huggingface-hub>=2.2.0,<3.0; python_version >= '3.10'",
|
|
27
|
+
"huggingface-hub>=0.34.0,<2.0; python_version < '3.10'",
|
|
27
28
|
"rich>=13.7,<14.0"
|
|
28
29
|
],
|
|
29
30
|
entry_points={
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# sima_cli/__version__.py
|
|
2
|
-
__version__ = "2.1.
|
|
2
|
+
__version__ = "2.1.19"
|
|
@@ -14,7 +14,7 @@ from sima_cli.update.swu import handle_update
|
|
|
14
14
|
from sima_cli.model_zoo.model import list_models, download_model, describe_model
|
|
15
15
|
from sima_cli.app_zoo.app import list_apps, download_app, describe_app
|
|
16
16
|
from sima_cli.utils.config_loader import internal_resource_exists
|
|
17
|
-
from sima_cli.mla.meminfo import monitor_simaai_mem_chart
|
|
17
|
+
from sima_cli.mla.meminfo import MemoryTelemetryError, monitor_simaai_mem_chart
|
|
18
18
|
from sima_cli.__version__ import __version__
|
|
19
19
|
from sima_cli.utils.config import CONFIG_PATH
|
|
20
20
|
from sima_cli.install.optiview import install_optiview
|
|
@@ -42,6 +42,7 @@ from sima_cli.upgrade.selfupdate import register_selfupdate_command
|
|
|
42
42
|
from sima_cli.playbooks import register_playbook_commands
|
|
43
43
|
from sima_cli.vulcan import register_vulcan_commands
|
|
44
44
|
from sima_cli.models import register_models_commands
|
|
45
|
+
from sima_cli.peripherals import register_peripherals_commands
|
|
45
46
|
from sima_cli.cloudex import register_cloudex_commands
|
|
46
47
|
from sima_cli.vulcan.commands import (
|
|
47
48
|
ENV_METAVAR,
|
|
@@ -105,6 +106,13 @@ def _rerun_current_command() -> None:
|
|
|
105
106
|
os.execvpe(sys.executable, cmd, env)
|
|
106
107
|
|
|
107
108
|
|
|
109
|
+
# Command groups whose stdout is a machine-readable document: root diagnostics go to stderr for them.
|
|
110
|
+
QUIET_STDOUT_SUBCOMMANDS = ("models", "peripherals")
|
|
111
|
+
# Command groups that only read the DevKit they run on: no PyPI update check and no Artifactory
|
|
112
|
+
# gate before them, so they work offline and never prompt or exit early.
|
|
113
|
+
LOCAL_ONLY_SUBCOMMANDS = ("peripherals",)
|
|
114
|
+
|
|
115
|
+
|
|
108
116
|
def _initialize_main_context(ctx, internal, yes):
|
|
109
117
|
_configure_stdio_errors()
|
|
110
118
|
auto_accept_update = yes or _update_auto_confirm_requested(sys.argv)
|
|
@@ -112,7 +120,8 @@ def _initialize_main_context(ctx, internal, yes):
|
|
|
112
120
|
if auto_accept_update:
|
|
113
121
|
os.environ["SIMA_CLI_AUTO_ACCEPT_UPDATE"] = "1"
|
|
114
122
|
try:
|
|
115
|
-
if
|
|
123
|
+
if (ctx.invoked_subcommand not in LOCAL_ONLY_SUBCOMMANDS
|
|
124
|
+
and check_for_update('sima-cli') and _should_rerun_after_update(sys.argv)):
|
|
116
125
|
_rerun_current_command()
|
|
117
126
|
finally:
|
|
118
127
|
if auto_accept_update:
|
|
@@ -128,14 +137,15 @@ def _initialize_main_context(ctx, internal, yes):
|
|
|
128
137
|
if not internal:
|
|
129
138
|
internal = os.getenv("SIMA_CLI_INTERNAL", "0") in ("1", "true", "yes")
|
|
130
139
|
|
|
131
|
-
|
|
140
|
+
local_only = ctx.invoked_subcommand in LOCAL_ONLY_SUBCOMMANDS
|
|
141
|
+
defer_artifactory_access = local_only or ctx.invoked_subcommand == "update" or ctx.meta.get("daily_netboot", False)
|
|
132
142
|
if internal and not internal_resource_exists() and not defer_artifactory_access:
|
|
133
143
|
click.echo("❌ You have specified -i or --internal argument to access internal resources, but you do not have an internal resource map configured.")
|
|
134
144
|
click.echo("Refer to the confluence page to find out how to configure internal resource map.")
|
|
135
145
|
exit(0)
|
|
136
146
|
|
|
137
147
|
internal_reachable = True
|
|
138
|
-
if internal:
|
|
148
|
+
if internal and not local_only:
|
|
139
149
|
internal_reachable = check_artifactory_reachability()
|
|
140
150
|
|
|
141
151
|
if internal and not internal_reachable and not defer_artifactory_access and not _allows_external_prerelease_fallback(sys.argv):
|
|
@@ -170,7 +180,7 @@ class InspectionAwareGroup(click.Group):
|
|
|
170
180
|
_command_name_from_argv(['sima-cli'] + options) == 'bootimg'
|
|
171
181
|
and any(arg in ('--netboot', '-n', '--autoflash', '-a') for arg in options)
|
|
172
182
|
)
|
|
173
|
-
if _command_name_from_argv(['sima-cli'] + options) == 'update' and '--inspect' in options:
|
|
183
|
+
if _command_name_from_argv(['sima-cli'] + options) == 'update' and any(arg in ('--inspect', '--swap') for arg in options):
|
|
174
184
|
ctx.meta['update_inspect'] = True
|
|
175
185
|
return super().parse_args(ctx, args)
|
|
176
186
|
|
|
@@ -198,11 +208,11 @@ def main(ctx, internal, yes):
|
|
|
198
208
|
ctx.ensure_object(dict)
|
|
199
209
|
ctx.obj.update(internal=internal, yes=yes)
|
|
200
210
|
return
|
|
201
|
-
# Model Registry JSON must remain parseable on stdout. Route root-level
|
|
202
|
-
# update and environment diagnostics to stderr for
|
|
211
|
+
# Model Registry and peripherals JSON must remain parseable on stdout. Route root-level
|
|
212
|
+
# update and environment diagnostics to stderr for these command groups.
|
|
203
213
|
output_context = (
|
|
204
214
|
redirect_stdout(sys.stderr)
|
|
205
|
-
if ctx.invoked_subcommand
|
|
215
|
+
if ctx.invoked_subcommand in QUIET_STDOUT_SUBCOMMANDS
|
|
206
216
|
else nullcontext()
|
|
207
217
|
)
|
|
208
218
|
with output_context:
|
|
@@ -216,6 +226,7 @@ register_sdk_commands(main)
|
|
|
216
226
|
register_playbook_commands(main)
|
|
217
227
|
register_vulcan_commands(main)
|
|
218
228
|
register_models_commands(main)
|
|
229
|
+
register_peripherals_commands(main)
|
|
219
230
|
register_cloudex_commands(main)
|
|
220
231
|
|
|
221
232
|
|
|
@@ -352,14 +363,15 @@ def download(ctx, url, dest):
|
|
|
352
363
|
"--dryrun",
|
|
353
364
|
is_flag=True,
|
|
354
365
|
default=False,
|
|
355
|
-
help="For eLxr
|
|
366
|
+
help="For eLxr, preview an update or slot swap without installing, switching slots, or rebooting."
|
|
356
367
|
)
|
|
357
368
|
@click.option("--inspect", "inspect_state", is_flag=True, help="Show eLxr 3.0+ A/B slots and overlay customizations without updating (local or --ip).")
|
|
369
|
+
@click.option("--swap", is_flag=True, help="Select the available inactive eLxr 3.0+ slot for next boot; use --reboot to restart now.")
|
|
358
370
|
@click.option("--verbose", is_flag=True, help="Show detailed overlay file categories during eLxr 3.0+ inspection or update.")
|
|
359
371
|
@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+).")
|
|
360
|
-
@click.option("--reboot", is_flag=True, help="Reboot after successful eLxr 3.0+ installation; verify remote boot health.")
|
|
372
|
+
@click.option("--reboot", is_flag=True, help="Reboot after successful eLxr 3.0+ installation or slot selection; verify remote boot health.")
|
|
361
373
|
@click.pass_context
|
|
362
|
-
def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force, troot_only, dryrun, inspect_state, verbose, reboot, signing_cert):
|
|
374
|
+
def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force, troot_only, dryrun, inspect_state, verbose, reboot, signing_cert, swap):
|
|
363
375
|
"""
|
|
364
376
|
Update a SiMa DevKit, remote device, or Linux PCIe host.
|
|
365
377
|
|
|
@@ -370,6 +382,15 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
370
382
|
|
|
371
383
|
eLxr 3.0+ uses signed full-system SWU bundles. Use --inspect for A/B
|
|
372
384
|
state, --ip for remote updates, and --reboot to reboot after installation.
|
|
385
|
+
|
|
386
|
+
Use --swap to select the valid inactive slot without downloading an update.
|
|
387
|
+
The current and target versions are shown before confirmation; --yes skips
|
|
388
|
+
confirmation. Reboot separately to apply, or add --reboot to restart now.
|
|
389
|
+
Shared data and overlay customizations persist and can override files in
|
|
390
|
+
the selected version. Blank, invalid, pending, and unknown states are refused.
|
|
391
|
+
|
|
392
|
+
Examples: sima-cli update --swap; sima-cli update --ip 192.168.6.5 --swap --reboot
|
|
393
|
+
|
|
373
394
|
The verification certificate is downloaded for the selected channel when configured; use
|
|
374
395
|
--signing-cert URL_OR_FILE for an image signed with your own certificate.
|
|
375
396
|
Developer-portal version lookup for 3.0 is not available yet.
|
|
@@ -479,6 +500,7 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
479
500
|
dryrun
|
|
480
501
|
or troot_only
|
|
481
502
|
or inspect_state
|
|
503
|
+
or swap
|
|
482
504
|
or verbose
|
|
483
505
|
or reboot
|
|
484
506
|
or force
|
|
@@ -496,6 +518,8 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
496
518
|
return
|
|
497
519
|
|
|
498
520
|
is_elxr = is_devkit_running_elxr()
|
|
521
|
+
if swap and (inspect_state or version_or_url or force or troot_only or signing_cert is not None or flavor != "auto"):
|
|
522
|
+
raise click.UsageError("--swap cannot be combined with --inspect or installation options.")
|
|
499
523
|
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'):
|
|
500
524
|
raise click.UsageError("--inspect cannot be combined with installation options.")
|
|
501
525
|
try:
|
|
@@ -503,7 +527,7 @@ def update(ctx, version_or_url, version_option, ip, yes, passwd, flavor, force,
|
|
|
503
527
|
internal=ctx.obj.get("internal", False),
|
|
504
528
|
auto_confirm=yes or ctx.obj.get("yes", False), dryrun=dryrun,
|
|
505
529
|
signing_cert=signing_cert, reboot=reboot, inspect=inspect_state, force=force,
|
|
506
|
-
troot_only=troot_only, flavor=flavor, local_elxr=is_elxr, verbose=verbose):
|
|
530
|
+
troot_only=troot_only, flavor=flavor, local_elxr=is_elxr, verbose=verbose, swap=swap):
|
|
507
531
|
return
|
|
508
532
|
except (click.ClickException, click.Abort):
|
|
509
533
|
raise
|
|
@@ -632,8 +656,10 @@ def mla(ctx):
|
|
|
632
656
|
@click.pass_context
|
|
633
657
|
def show_mla_memory_usage(ctx):
|
|
634
658
|
"""Show MLA Memory usage overtime."""
|
|
635
|
-
|
|
636
|
-
|
|
659
|
+
try:
|
|
660
|
+
monitor_simaai_mem_chart()
|
|
661
|
+
except MemoryTelemetryError as exc:
|
|
662
|
+
raise click.ClickException(str(exc)) from exc
|
|
637
663
|
|
|
638
664
|
|
|
639
665
|
# ----------------------
|
|
@@ -554,20 +554,14 @@ def _reconcile_existing_connection(profile, store, progress):
|
|
|
554
554
|
|
|
555
555
|
def connect(profile, store, progress, attempts=3, transport="auto"):
|
|
556
556
|
"""Create a session and return its durable local record."""
|
|
557
|
-
|
|
558
|
-
forwarder = find_forwarder()
|
|
559
|
-
except CloudExError:
|
|
560
|
-
# Import lazily: the installer reuses this module's privilege and error
|
|
561
|
-
# handling, while ordinary CloudEx commands should remain quick to load.
|
|
562
|
-
from .installer import DEFAULT_BRANCH, install_forwarder
|
|
557
|
+
from .installer import ensure_forwarder
|
|
563
558
|
|
|
564
|
-
progress.update("CloudEx forwarder is missing; installing from {}".format(DEFAULT_BRANCH))
|
|
565
|
-
forwarder = install_forwarder(DEFAULT_BRANCH, progress=progress)["path"]
|
|
566
559
|
authorize_admin()
|
|
567
560
|
api = CloudExAPI.from_profile(profile)
|
|
568
561
|
existing = _reconcile_existing_connection(profile, store, progress)
|
|
569
562
|
if existing:
|
|
570
563
|
return existing
|
|
564
|
+
forwarder = ensure_forwarder(progress=progress)
|
|
571
565
|
choices = (["p2p"] * attempts + ["routed"] if transport == "auto"
|
|
572
566
|
else ["routed"] if transport == "turn" else ["p2p"] * attempts)
|
|
573
567
|
last_error = None
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Install the native CloudEx forwarder from Kerrigan's Vulcan artifacts."""
|
|
2
2
|
|
|
3
3
|
import hashlib
|
|
4
|
+
import os
|
|
4
5
|
import platform
|
|
5
6
|
import re
|
|
6
7
|
import subprocess
|
|
@@ -99,7 +100,30 @@ def _download_resource(client, base_url, resource, expected_sha256, destination)
|
|
|
99
100
|
return path
|
|
100
101
|
|
|
101
102
|
|
|
102
|
-
def
|
|
103
|
+
def ensure_forwarder(progress=None):
|
|
104
|
+
"""Resolve the current published binary before starting a new tunnel."""
|
|
105
|
+
try:
|
|
106
|
+
installed = find_forwarder()
|
|
107
|
+
except CloudExError:
|
|
108
|
+
installed = None
|
|
109
|
+
else:
|
|
110
|
+
if os.environ.get("SIMA_CLOUDEX_FORWARDER"):
|
|
111
|
+
# Preserve an existing controlled binary, but allow the normal
|
|
112
|
+
# installer to bootstrap a missing controlled installation.
|
|
113
|
+
return installed
|
|
114
|
+
return install_forwarder(
|
|
115
|
+
DEFAULT_BRANCH, progress=progress, current_path=installed
|
|
116
|
+
)["path"]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _matches_binary(path, checksum):
|
|
120
|
+
try:
|
|
121
|
+
return hashlib.sha256(Path(path).read_bytes()).hexdigest() == checksum.lower()
|
|
122
|
+
except OSError:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def install_forwarder(branch=DEFAULT_BRANCH, progress=None, client=None, current_path=None):
|
|
103
127
|
"""Install or update the forwarder and return artifact/install details."""
|
|
104
128
|
branch = _validate_branch(branch)
|
|
105
129
|
variant = _platform_variant()
|
|
@@ -119,6 +143,12 @@ def install_forwarder(branch=DEFAULT_BRANCH, progress=None, client=None):
|
|
|
119
143
|
metadata, resources, script_resource = _checked_metadata(
|
|
120
144
|
client, result.metadata_url, variant
|
|
121
145
|
)
|
|
146
|
+
expected_sha256 = metadata["resources-checksum"][resources[0]]
|
|
147
|
+
if current_path is not None and _matches_binary(current_path, expected_sha256):
|
|
148
|
+
if progress:
|
|
149
|
+
progress.update("CloudEx forwarder is up to date")
|
|
150
|
+
return {"path": current_path, "branch": branch,
|
|
151
|
+
"version": metadata["version"], "artifact": result.resolved_spec}
|
|
122
152
|
base_url = result.metadata_url.rsplit("/", 1)[0]
|
|
123
153
|
with tempfile.TemporaryDirectory(prefix="sima-cloudex-forwarder-") as temporary:
|
|
124
154
|
root = Path(temporary)
|
|
@@ -156,6 +186,8 @@ def install_forwarder(branch=DEFAULT_BRANCH, progress=None, client=None):
|
|
|
156
186
|
raise CloudExError("CloudEx forwarder installation failed: {}".format(detail[:1024]))
|
|
157
187
|
|
|
158
188
|
installed = find_forwarder()
|
|
189
|
+
if current_path is not None and not _matches_binary(installed, expected_sha256):
|
|
190
|
+
raise CloudExError("CloudEx forwarder update did not replace the active binary; check PATH")
|
|
159
191
|
return {
|
|
160
192
|
"path": installed,
|
|
161
193
|
"branch": branch,
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import json
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from sima_cli.utils.streaming import DOWNLOAD_CHUNK_SIZE, publish_partial
|
|
7
|
+
from sima_cli.download.parallel import PARALLEL_MIN_SIZE, download_parallel
|
|
2
8
|
import requests
|
|
3
9
|
from urllib.parse import urlparse
|
|
4
10
|
from tqdm import tqdm
|
|
@@ -74,10 +80,11 @@ def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = Fa
|
|
|
74
80
|
os.makedirs(dest_folder, exist_ok=True)
|
|
75
81
|
dest_path = os.path.join(dest_folder, file_name)
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
partial = Path(dest_path + ".part")
|
|
84
|
+
receipt = Path(dest_path + ".part.json")
|
|
85
|
+
headers = {"Accept-Encoding": "identity"}
|
|
80
86
|
existing_size = 0
|
|
87
|
+
session = None
|
|
81
88
|
|
|
82
89
|
try:
|
|
83
90
|
if internal:
|
|
@@ -102,48 +109,98 @@ def download_file_from_url(url: str, dest_folder: str = ".", internal: bool = Fa
|
|
|
102
109
|
request_fn = session.get
|
|
103
110
|
head_fn = session.head
|
|
104
111
|
|
|
105
|
-
#
|
|
106
|
-
head = head_fn(url, headers=headers, timeout=10)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
# Only resume a partial when a strong validator identifies its bytes.
|
|
113
|
+
head = head_fn(url, headers=headers, timeout=10, allow_redirects=True)
|
|
114
|
+
try:
|
|
115
|
+
head.raise_for_status()
|
|
116
|
+
raw_size = head.headers.get('content-length')
|
|
117
|
+
total_size = int(raw_size) if raw_size is not None else None
|
|
118
|
+
etag = head.headers.get('ETag', '')
|
|
119
|
+
accepts_ranges = head.headers.get('Accept-Ranges', '').lower() == 'bytes'
|
|
120
|
+
finally:
|
|
121
|
+
head.close()
|
|
122
|
+
if etag.startswith('W/'):
|
|
123
|
+
etag = ''
|
|
124
|
+
identity = {"url_sha256": hashlib.sha256(url.encode()).hexdigest(), "etag": etag}
|
|
125
|
+
if os.path.exists(dest_path) and total_size is not None:
|
|
126
|
+
if os.path.getsize(dest_path) == total_size:
|
|
115
127
|
print(f"✔ File already exists and is complete: {file_name}")
|
|
116
128
|
return dest_path
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
if partial.exists() and etag:
|
|
130
|
+
try:
|
|
131
|
+
if json.loads(receipt.read_text()) == identity:
|
|
132
|
+
existing_size = partial.stat().st_size
|
|
133
|
+
except (OSError, ValueError):
|
|
134
|
+
pass
|
|
135
|
+
if total_size is not None and existing_size >= total_size:
|
|
136
|
+
existing_size = 0
|
|
137
|
+
# Keep existing contiguous partials resumable. Parallelism is limited
|
|
138
|
+
# to the public mirror; authenticated/custom sessions retain their path.
|
|
139
|
+
if (not existing_size and parsed_url.scheme == 'https'
|
|
140
|
+
and parsed_url.hostname == 'artifacts.neat.sima.ai'
|
|
141
|
+
and not internal and total_size is not None
|
|
142
|
+
and total_size >= PARALLEL_MIN_SIZE and etag and accepts_ranges):
|
|
143
|
+
if download_parallel(url, dest_path, total_size, etag):
|
|
144
|
+
partial.unlink(missing_ok=True)
|
|
145
|
+
receipt.unlink(missing_ok=True)
|
|
146
|
+
return dest_path
|
|
147
|
+
if existing_size:
|
|
148
|
+
headers['Range'] = f'bytes={existing_size}-'
|
|
149
|
+
headers['If-Range'] = etag
|
|
124
150
|
|
|
125
|
-
# Begin download with appropriate handler
|
|
126
151
|
with request_fn(url, stream=True, headers=headers, timeout=30) as r:
|
|
127
152
|
r.raise_for_status()
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
if r.headers.get('Content-Encoding', 'identity').lower() != 'identity':
|
|
154
|
+
raise ValueError("Server ignored identity content encoding")
|
|
155
|
+
raw_length = r.headers.get('content-length')
|
|
156
|
+
content_length = int(raw_length) if raw_length is not None else None
|
|
157
|
+
if r.status_code == 206:
|
|
158
|
+
match = re.fullmatch(r'bytes (\d+)-(\d+)/(\d+)', r.headers.get('Content-Range', ''))
|
|
159
|
+
if not existing_size or not match:
|
|
160
|
+
raise ValueError("Invalid partial download response")
|
|
161
|
+
start, end, size = map(int, match.groups())
|
|
162
|
+
if (start != existing_size or end != size - 1 or end < start
|
|
163
|
+
or (content_length is not None and content_length != end - start + 1)
|
|
164
|
+
or (total_size is not None and size != total_size)
|
|
165
|
+
or (r.headers.get('ETag') and r.headers['ETag'] != etag)):
|
|
166
|
+
raise ValueError("Inconsistent partial download response")
|
|
167
|
+
final_total = size
|
|
168
|
+
elif r.status_code == 200:
|
|
169
|
+
# Range ignored, or If-Range detected a changed object: restart.
|
|
170
|
+
existing_size = 0
|
|
171
|
+
final_total = content_length if content_length is not None else total_size
|
|
172
|
+
else:
|
|
173
|
+
raise ValueError(f"Unexpected download status: {r.status_code}")
|
|
174
|
+
response_etag = r.headers.get('ETag', '')
|
|
175
|
+
if existing_size:
|
|
176
|
+
response_etag = etag
|
|
177
|
+
identity['etag'] = response_etag if not response_etag.startswith('W/') else ''
|
|
178
|
+
# Truncate before updating the receipt so old bytes cannot acquire a new validator.
|
|
179
|
+
with partial.open('ab' if existing_size else 'wb') as f, tqdm(
|
|
180
|
+
desc=f"⬇️ Downloading {file_name}", total=final_total,
|
|
181
|
+
initial=existing_size, unit='B', unit_scale=True, unit_divisor=1024
|
|
139
182
|
) as bar:
|
|
140
|
-
|
|
183
|
+
receipt.write_text(json.dumps(identity), encoding='utf-8')
|
|
184
|
+
size = existing_size
|
|
185
|
+
for chunk in r.iter_content(chunk_size=DOWNLOAD_CHUNK_SIZE):
|
|
141
186
|
if chunk:
|
|
142
187
|
f.write(chunk)
|
|
188
|
+
size += len(chunk)
|
|
143
189
|
bar.update(len(chunk))
|
|
190
|
+
if final_total is not None and size > final_total:
|
|
191
|
+
raise ValueError("Download exceeds expected size")
|
|
192
|
+
if final_total is not None and size != final_total:
|
|
193
|
+
raise ValueError("Download size mismatch")
|
|
194
|
+
f.flush()
|
|
195
|
+
os.fsync(f.fileno())
|
|
196
|
+
publish_partial(partial, dest_path)
|
|
197
|
+
receipt.unlink(missing_ok=True)
|
|
144
198
|
|
|
145
199
|
except Exception as e:
|
|
146
200
|
raise RuntimeError(f"Download failed: {e}")
|
|
201
|
+
finally:
|
|
202
|
+
if session is not None:
|
|
203
|
+
session.close()
|
|
147
204
|
|
|
148
205
|
|
|
149
206
|
return dest_path
|