sima-cli 2.1.7__tar.gz → 2.1.8__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.7/sima_cli.egg-info → sima_cli-2.1.8}/PKG-INFO +18 -5
- {sima_cli-2.1.7 → sima_cli-2.1.8}/README.md +17 -4
- {sima_cli-2.1.7 → sima_cli-2.1.8}/pyproject.toml +1 -1
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/__version__.py +1 -1
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/cli.py +46 -15
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/metadata_installer.py +198 -8
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/package_builder.py +5 -1
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/registry.py +18 -1
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/install.py +42 -5
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/utils.py +2 -0
- sima_cli-2.1.8/sima_cli/upgrade/selfupdate.py +408 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/vulcan/artifacts.py +1 -28
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/vulcan/commands.py +114 -36
- {sima_cli-2.1.7 → sima_cli-2.1.8/sima_cli.egg-info}/PKG-INFO +18 -5
- sima_cli-2.1.8/tests/unit/test_metadata_installer.py +207 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_package_builder.py +45 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_sdk_image_detection.py +70 -0
- sima_cli-2.1.8/tests/unit/test_selfupdate.py +181 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_vulcan.py +108 -29
- sima_cli-2.1.7/sima_cli/upgrade/selfupdate.py +0 -236
- sima_cli-2.1.7/tests/unit/test_metadata_installer.py +0 -21
- sima_cli-2.1.7/tests/unit/test_selfupdate.py +0 -68
- {sima_cli-2.1.7 → sima_cli-2.1.8}/LICENSE +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/MANIFEST.in +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/requirements.txt +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/setup.cfg +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/setup.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/__main__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/app_zoo/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/app_zoo/app.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/app_zoo/commands.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/auth/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/auth/auth0.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/auth/devportal.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/auth/login.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/auth/oauth.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/data/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/data/resources_internal.yaml +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/data/resources_public.yaml +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/deploy_only/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/deploy_only/device/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/deploy_only/device/commands.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/deploy_only/mpk/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/deploy_only/mpk/commands.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/discover/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/discover/discover.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/discover/linuxll.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/download/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/download/downloader.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/github_assets.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/hostdriver.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/metadata_info.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/metadata_validator.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/optiview.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/install/palette.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/mla/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/mla/meminfo.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/model_zoo/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/model_zoo/model.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/network/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/network/network.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/playbooks/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/playbooks/commands.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/playbooks/manager.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/cmdexec.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/commands.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/config.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/linux_shared_network.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/neat.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/preinstall.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/requirements.json +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/script.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/stop.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/sdk/uninstall.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/serial/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/serial/serial.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/storage/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/storage/nvme.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/storage/sdcard.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/bmaptool.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/bootimg.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/cleanlog.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/elxr.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/local.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/netboot.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/query.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/remote.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/update/updater.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/upgrade/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/api_common.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/artifactory.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/common.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/config.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/config_loader.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/container_registries.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/device_api.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/disk.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/docker.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/env.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/errors.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/mpk_api.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/net.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/network.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/pcie.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/pkg_update_check.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/serializers.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/services.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/utils/tag.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli/vulcan/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli.egg-info/SOURCES.txt +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli.egg-info/dependency_links.txt +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli.egg-info/entry_points.txt +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli.egg-info/requires.txt +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/sima_cli.egg-info/top_level.txt +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/e2e/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/__init__.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_app_zoo.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_auth.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_cli.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_cli_stdio.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_download.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_elxr_update.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_firmware.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_install_stub.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_model_zoo.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_netboot.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_pkg_update_check.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_sdk_preinstall.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_sdk_uninstall.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_skills_commands.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/tests/unit/test_skills_manager.py +0 -0
- {sima_cli-2.1.7 → sima_cli-2.1.8}/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.8
|
|
4
4
|
Summary: CLI tool for SiMa Developer Portal to download models, firmware, and apps.
|
|
5
5
|
Home-page: https://developer.sima.ai/
|
|
6
6
|
Author: SiMa.ai
|
|
@@ -163,10 +163,11 @@ sima-cli vulcan download --env production core main
|
|
|
163
163
|
- `dev`: `https://artifacts.neat.paconsultings.com`
|
|
164
164
|
- `staging`: `https://artifacts.stg.neat.sima.ai`
|
|
165
165
|
- `production`: `https://artifacts.neat.sima.ai`
|
|
166
|
-
- `
|
|
166
|
+
- `dev` and `staging` are available for Vulcan downloads. `production` is not yet available.
|
|
167
167
|
- Usage:
|
|
168
|
-
- `sima-cli vulcan --env {dev|staging|production} download [REPO] [BRANCH_OR_TAG]`
|
|
169
|
-
- `sima-cli vulcan download --env {dev|staging|production} [REPO] [BRANCH_OR_TAG]`
|
|
168
|
+
- `sima-cli vulcan --env {dev|stg|staging|prd|prod|production} download [REPO] [BRANCH_OR_TAG]`
|
|
169
|
+
- `sima-cli vulcan download --env {dev|stg|staging|prd|prod|production} [REPO] [BRANCH_OR_TAG]`
|
|
170
|
+
- Shortcut flags are also available: `--dev`, `--stg`/`--staging`, and `--prd`/`--prod`.
|
|
170
171
|
- If `REPO` is omitted, the CLI prompts for a repository.
|
|
171
172
|
- If `BRANCH_OR_TAG` is omitted, the CLI downloads `branches.json` and prompts for a branch.
|
|
172
173
|
- For automation, pass both values and add `--json` for structured output.
|
|
@@ -587,7 +588,19 @@ sima-cli selfupdate
|
|
|
587
588
|
sima-cli selfupdate --dev
|
|
588
589
|
```
|
|
589
590
|
|
|
590
|
-
- Update from
|
|
591
|
+
- Update from Vulcan dev artifacts. If `--branch` is omitted, sima-cli loads `branches.json` and prompts for a branch.
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
sima-cli selfupdate --stg --branch main
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
- Update from Vulcan staging artifacts.
|
|
598
|
+
|
|
599
|
+
```bash
|
|
600
|
+
sima-cli selfupdate --neat --branch main
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
- Update from Vulcan production artifacts. Production aliases are `--prd`, `--prod`, `--neat`, and `--vulcan`.
|
|
591
604
|
|
|
592
605
|
```bash
|
|
593
606
|
sima-cli selfupdate -v 0.0.46
|
|
@@ -129,10 +129,11 @@ sima-cli vulcan download --env production core main
|
|
|
129
129
|
- `dev`: `https://artifacts.neat.paconsultings.com`
|
|
130
130
|
- `staging`: `https://artifacts.stg.neat.sima.ai`
|
|
131
131
|
- `production`: `https://artifacts.neat.sima.ai`
|
|
132
|
-
- `
|
|
132
|
+
- `dev` and `staging` are available for Vulcan downloads. `production` is not yet available.
|
|
133
133
|
- Usage:
|
|
134
|
-
- `sima-cli vulcan --env {dev|staging|production} download [REPO] [BRANCH_OR_TAG]`
|
|
135
|
-
- `sima-cli vulcan download --env {dev|staging|production} [REPO] [BRANCH_OR_TAG]`
|
|
134
|
+
- `sima-cli vulcan --env {dev|stg|staging|prd|prod|production} download [REPO] [BRANCH_OR_TAG]`
|
|
135
|
+
- `sima-cli vulcan download --env {dev|stg|staging|prd|prod|production} [REPO] [BRANCH_OR_TAG]`
|
|
136
|
+
- Shortcut flags are also available: `--dev`, `--stg`/`--staging`, and `--prd`/`--prod`.
|
|
136
137
|
- If `REPO` is omitted, the CLI prompts for a repository.
|
|
137
138
|
- If `BRANCH_OR_TAG` is omitted, the CLI downloads `branches.json` and prompts for a branch.
|
|
138
139
|
- For automation, pass both values and add `--json` for structured output.
|
|
@@ -553,7 +554,19 @@ sima-cli selfupdate
|
|
|
553
554
|
sima-cli selfupdate --dev
|
|
554
555
|
```
|
|
555
556
|
|
|
556
|
-
- Update from
|
|
557
|
+
- Update from Vulcan dev artifacts. If `--branch` is omitted, sima-cli loads `branches.json` and prompts for a branch.
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
sima-cli selfupdate --stg --branch main
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
- Update from Vulcan staging artifacts.
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
sima-cli selfupdate --neat --branch main
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
- Update from Vulcan production artifacts. Production aliases are `--prd`, `--prod`, `--neat`, and `--vulcan`.
|
|
557
570
|
|
|
558
571
|
```bash
|
|
559
572
|
sima-cli selfupdate -v 0.0.46
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# sima_cli/__version__.py
|
|
2
|
-
__version__ = "2.1.
|
|
2
|
+
__version__ = "2.1.8"
|
|
@@ -30,7 +30,13 @@ from sima_cli.install.registry import register_packages_commands
|
|
|
30
30
|
from sima_cli.upgrade.selfupdate import register_selfupdate_command
|
|
31
31
|
from sima_cli.playbooks import register_playbook_commands
|
|
32
32
|
from sima_cli.vulcan import register_vulcan_commands
|
|
33
|
-
from sima_cli.vulcan.commands import
|
|
33
|
+
from sima_cli.vulcan.commands import (
|
|
34
|
+
ENV_METAVAR,
|
|
35
|
+
_environment_shortcut_options,
|
|
36
|
+
_normalize_environment_option,
|
|
37
|
+
_resolve_environment,
|
|
38
|
+
install_vulcan_package,
|
|
39
|
+
)
|
|
34
40
|
|
|
35
41
|
def _configure_stdio_errors() -> None:
|
|
36
42
|
for stream in (getattr(sys, "stdout", None), getattr(sys, "stderr", None)):
|
|
@@ -505,21 +511,29 @@ ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
|
|
|
505
511
|
@click.argument("component", required=False)
|
|
506
512
|
@click.option("-v", "--version", help="SDK version (required for SDK-dependent components unless --metadata is provided)")
|
|
507
513
|
@click.option("-m", "--mirror", help="URL to a metadata.json file for generic installation")
|
|
508
|
-
@click.option("-t", "--tag", help="Tag of the package. With --
|
|
509
|
-
@click.option("--
|
|
514
|
+
@click.option("-t", "--tag", help="Tag of the package. With --neat, metadata variant type such as minimum.")
|
|
515
|
+
@click.option("--neat", "use_neat", is_flag=True, help="Install from Neat artifacts using the Neat package resolver.")
|
|
516
|
+
@click.option(
|
|
517
|
+
"--vulcan",
|
|
518
|
+
"use_vulcan",
|
|
519
|
+
is_flag=True,
|
|
520
|
+
help="Install from Neat/Vulcan artifacts. Compatibility alias for --neat.",
|
|
521
|
+
)
|
|
522
|
+
@_environment_shortcut_options
|
|
510
523
|
@click.option(
|
|
511
524
|
"--env",
|
|
512
525
|
"vulcan_environment",
|
|
513
|
-
|
|
526
|
+
metavar=ENV_METAVAR,
|
|
527
|
+
callback=_normalize_environment_option,
|
|
514
528
|
default=None,
|
|
515
|
-
help="
|
|
529
|
+
help="Neat artifact environment. Used with --neat or --vulcan. Defaults to production.",
|
|
516
530
|
)
|
|
517
531
|
@click.option(
|
|
518
532
|
"--base-url",
|
|
519
533
|
"vulcan_base_url",
|
|
520
534
|
default=None,
|
|
521
|
-
envvar="
|
|
522
|
-
help="Override the
|
|
535
|
+
envvar="SIMA_NEAT_BASE_URL",
|
|
536
|
+
help="Override the Neat artifact base URL. Used with --neat or --vulcan.",
|
|
523
537
|
)
|
|
524
538
|
@click.option(
|
|
525
539
|
"-d",
|
|
@@ -527,9 +541,9 @@ ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
|
|
|
527
541
|
default=".",
|
|
528
542
|
show_default=True,
|
|
529
543
|
type=click.Path(file_okay=False, dir_okay=True, path_type=str),
|
|
530
|
-
help="Directory where
|
|
544
|
+
help="Directory where Neat package resources are downloaded and installed. Used with --neat or --vulcan.",
|
|
531
545
|
)
|
|
532
|
-
@click.option("--json", "json_output", is_flag=True, help="With --vulcan, print resolved metadata URL and exit.")
|
|
546
|
+
@click.option("--json", "json_output", is_flag=True, help="With --neat or --vulcan, print resolved metadata URL and exit.")
|
|
533
547
|
@click.option(
|
|
534
548
|
"-f",
|
|
535
549
|
"--force",
|
|
@@ -538,7 +552,21 @@ ALL_COMPONENTS = SDK_DEPENDENT_COMPONENTS | SDK_INDEPENDENT_COMPONENTS
|
|
|
538
552
|
help="Force installation even if compatibility checks fail.",
|
|
539
553
|
)
|
|
540
554
|
@click.pass_context
|
|
541
|
-
def install_cmd(
|
|
555
|
+
def install_cmd(
|
|
556
|
+
ctx,
|
|
557
|
+
component,
|
|
558
|
+
version,
|
|
559
|
+
mirror,
|
|
560
|
+
tag,
|
|
561
|
+
use_neat,
|
|
562
|
+
use_vulcan,
|
|
563
|
+
vulcan_environment,
|
|
564
|
+
environment_flag,
|
|
565
|
+
vulcan_base_url,
|
|
566
|
+
install_dir,
|
|
567
|
+
json_output,
|
|
568
|
+
force,
|
|
569
|
+
):
|
|
542
570
|
"""
|
|
543
571
|
Install SiMa packages.
|
|
544
572
|
|
|
@@ -573,15 +601,18 @@ def install_cmd(ctx, component, version, mirror, tag, use_vulcan, vulcan_environ
|
|
|
573
601
|
"""
|
|
574
602
|
internal = ctx.obj.get("internal", False)
|
|
575
603
|
|
|
576
|
-
|
|
604
|
+
use_neat_resolver = use_neat or use_vulcan
|
|
605
|
+
if use_neat_resolver:
|
|
606
|
+
if use_neat and use_vulcan:
|
|
607
|
+
raise click.ClickException("Use only one of --neat or --vulcan.")
|
|
577
608
|
if mirror:
|
|
578
|
-
raise click.ClickException("--mirror cannot be used with --
|
|
609
|
+
raise click.ClickException("--mirror cannot be used with --neat.")
|
|
579
610
|
if not component:
|
|
580
|
-
raise click.ClickException("You must specify a
|
|
611
|
+
raise click.ClickException("You must specify a Neat target when using --neat.")
|
|
581
612
|
install_vulcan_package(
|
|
582
613
|
target=component,
|
|
583
|
-
environment=vulcan_environment
|
|
584
|
-
base_url=vulcan_base_url,
|
|
614
|
+
environment=_resolve_environment(vulcan_environment, environment_flag),
|
|
615
|
+
base_url=vulcan_base_url or os.getenv("SIMA_VULCAN_BASE_URL"),
|
|
585
616
|
package_type=tag,
|
|
586
617
|
install_dir=install_dir,
|
|
587
618
|
force=force,
|
|
@@ -11,7 +11,7 @@ import stat
|
|
|
11
11
|
import shlex
|
|
12
12
|
import platform
|
|
13
13
|
import hashlib
|
|
14
|
-
from urllib.parse import urlparse, quote, urljoin
|
|
14
|
+
from urllib.parse import urlparse, quote, urljoin, unquote
|
|
15
15
|
from typing import Dict
|
|
16
16
|
from tqdm import tqdm
|
|
17
17
|
from pathlib import Path
|
|
@@ -280,6 +280,169 @@ def _compute_sha256(file_path: Path, chunk_size: int = 1024 * 1024) -> str:
|
|
|
280
280
|
hasher.update(chunk)
|
|
281
281
|
return hasher.hexdigest()
|
|
282
282
|
|
|
283
|
+
def _resolve_resource_url(base_url: str, resource: str) -> str:
|
|
284
|
+
"""
|
|
285
|
+
Resolve a metadata resource to a downloadable URL.
|
|
286
|
+
|
|
287
|
+
Metadata resources are file names or relative paths. Encode each path segment
|
|
288
|
+
so URL-reserved characters that are valid in artifact names, such as '+', do
|
|
289
|
+
not get interpreted by object storage/CDNs as a different key.
|
|
290
|
+
"""
|
|
291
|
+
parsed_resource = urlparse(resource)
|
|
292
|
+
if parsed_resource.scheme or parsed_resource.netloc:
|
|
293
|
+
return resource
|
|
294
|
+
|
|
295
|
+
encoded_resource = "/".join(
|
|
296
|
+
quote(segment, safe="")
|
|
297
|
+
for segment in resource.split("/")
|
|
298
|
+
)
|
|
299
|
+
return urljoin(base_url, encoded_resource)
|
|
300
|
+
|
|
301
|
+
def _resolve_resource_url_candidates(base_url: str, resource: str) -> list[str]:
|
|
302
|
+
primary_url = _resolve_resource_url(base_url, resource)
|
|
303
|
+
|
|
304
|
+
parsed_resource = urlparse(resource)
|
|
305
|
+
if parsed_resource.scheme or parsed_resource.netloc or "%" not in resource:
|
|
306
|
+
return [primary_url]
|
|
307
|
+
|
|
308
|
+
percent_preserving_resource = "/".join(
|
|
309
|
+
quote(segment, safe="%")
|
|
310
|
+
for segment in resource.split("/")
|
|
311
|
+
)
|
|
312
|
+
fallback_url = urljoin(base_url, percent_preserving_resource)
|
|
313
|
+
if fallback_url == primary_url:
|
|
314
|
+
return [primary_url]
|
|
315
|
+
return [primary_url, fallback_url]
|
|
316
|
+
|
|
317
|
+
def _metadata_resource_path(dest_folder: str, resource: str, resource_url: str) -> Path:
|
|
318
|
+
parsed_resource = urlparse(resource)
|
|
319
|
+
if parsed_resource.scheme or parsed_resource.netloc:
|
|
320
|
+
file_name = os.path.basename(urlparse(resource_url).path)
|
|
321
|
+
else:
|
|
322
|
+
file_name = os.path.basename(resource)
|
|
323
|
+
|
|
324
|
+
if not file_name:
|
|
325
|
+
raise click.ClickException(f"❌ Cannot determine file name for resource '{resource}'.")
|
|
326
|
+
|
|
327
|
+
return Path(dest_folder) / file_name
|
|
328
|
+
|
|
329
|
+
def _normalize_downloaded_metadata_resource(local_path: str, expected_path: Path) -> str:
|
|
330
|
+
downloaded_path = Path(local_path)
|
|
331
|
+
if downloaded_path == expected_path:
|
|
332
|
+
return local_path
|
|
333
|
+
|
|
334
|
+
if expected_path.exists():
|
|
335
|
+
expected_path.unlink()
|
|
336
|
+
downloaded_path.rename(expected_path)
|
|
337
|
+
return str(expected_path)
|
|
338
|
+
|
|
339
|
+
def _download_metadata_file_resource(
|
|
340
|
+
resource: str,
|
|
341
|
+
resource_urls: list[str],
|
|
342
|
+
dest_folder: str,
|
|
343
|
+
dest_path: Path,
|
|
344
|
+
internal: bool,
|
|
345
|
+
) -> str:
|
|
346
|
+
errors: list[str] = []
|
|
347
|
+
for index, resource_url in enumerate(resource_urls):
|
|
348
|
+
try:
|
|
349
|
+
local_path = download_file_from_url(
|
|
350
|
+
url=resource_url,
|
|
351
|
+
dest_folder=dest_folder,
|
|
352
|
+
internal=internal
|
|
353
|
+
)
|
|
354
|
+
return _normalize_downloaded_metadata_resource(local_path, dest_path)
|
|
355
|
+
except Exception as e:
|
|
356
|
+
errors.append(f"{resource_url}: {e}")
|
|
357
|
+
if index < len(resource_urls) - 1:
|
|
358
|
+
click.echo(
|
|
359
|
+
f"⚠️ Download failed for encoded resource URL; retrying percent-preserving URL for '{resource}'."
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
raise click.ClickException("; ".join(errors))
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _resource_basename(resource: str) -> str:
|
|
366
|
+
parsed = urlparse(resource)
|
|
367
|
+
path = parsed.path if parsed.scheme or parsed.netloc else resource
|
|
368
|
+
return unquote(os.path.basename(path)).lower()
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _is_wheel_resource(resource: str) -> bool:
|
|
372
|
+
return _resource_basename(resource).endswith(".whl")
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _wheel_platform_tag(resource: str) -> str:
|
|
376
|
+
name = _resource_basename(resource)
|
|
377
|
+
if not name.endswith(".whl"):
|
|
378
|
+
return ""
|
|
379
|
+
parts = name[:-4].split("-")
|
|
380
|
+
if len(parts) < 5:
|
|
381
|
+
return ""
|
|
382
|
+
return parts[-1].lower()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _current_wheel_platform() -> tuple:
|
|
386
|
+
system = platform.system().lower()
|
|
387
|
+
machine = platform.machine().lower()
|
|
388
|
+
|
|
389
|
+
if system == "darwin":
|
|
390
|
+
os_family = "mac"
|
|
391
|
+
elif system == "windows":
|
|
392
|
+
os_family = "windows"
|
|
393
|
+
elif system == "linux":
|
|
394
|
+
os_family = "linux"
|
|
395
|
+
else:
|
|
396
|
+
os_family = system
|
|
397
|
+
|
|
398
|
+
if machine in {"x86_64", "amd64"}:
|
|
399
|
+
arch_tokens = {"x86_64", "amd64"}
|
|
400
|
+
elif machine in {"aarch64", "arm64"}:
|
|
401
|
+
arch_tokens = {"aarch64", "arm64"}
|
|
402
|
+
elif machine in {"i386", "i686", "x86"}:
|
|
403
|
+
arch_tokens = {"i386", "i686", "x86", "win32"}
|
|
404
|
+
else:
|
|
405
|
+
arch_tokens = {machine} if machine else set()
|
|
406
|
+
|
|
407
|
+
return os_family, arch_tokens
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _wheel_arch_matches(platform_tag: str, arch_tokens: set) -> bool:
|
|
411
|
+
known_arch_tokens = {"x86_64", "amd64", "aarch64", "arm64", "i386", "i686", "x86", "win32"}
|
|
412
|
+
present_tokens = {token for token in known_arch_tokens if token in platform_tag}
|
|
413
|
+
return not present_tokens or bool(present_tokens & arch_tokens)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _is_compatible_wheel_resource(resource: str) -> bool:
|
|
417
|
+
platform_tag = _wheel_platform_tag(resource)
|
|
418
|
+
if not platform_tag:
|
|
419
|
+
return True
|
|
420
|
+
if platform_tag in {"any", "none"}:
|
|
421
|
+
return True
|
|
422
|
+
|
|
423
|
+
os_family, arch_tokens = _current_wheel_platform()
|
|
424
|
+
if os_family == "mac":
|
|
425
|
+
os_matches = platform_tag.startswith("macosx") or "macos" in platform_tag
|
|
426
|
+
elif os_family == "windows":
|
|
427
|
+
os_matches = platform_tag.startswith("win") or platform_tag in {"win32", "win_amd64", "win_arm64"}
|
|
428
|
+
elif os_family == "linux":
|
|
429
|
+
os_matches = "linux" in platform_tag
|
|
430
|
+
else:
|
|
431
|
+
os_matches = False
|
|
432
|
+
|
|
433
|
+
return os_matches and _wheel_arch_matches(platform_tag, arch_tokens)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _filter_download_compatible_resources(resources: list) -> list:
|
|
437
|
+
filtered = []
|
|
438
|
+
for resource in resources:
|
|
439
|
+
if _is_wheel_resource(resource) and not _is_compatible_wheel_resource(resource):
|
|
440
|
+
click.echo(f"⏭️ Skipping incompatible wheel for this platform: {resource}")
|
|
441
|
+
continue
|
|
442
|
+
filtered.append(resource)
|
|
443
|
+
return filtered
|
|
444
|
+
|
|
445
|
+
|
|
283
446
|
def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal: bool = False, skip_models: bool = False, tag: str = None) -> list:
|
|
284
447
|
"""
|
|
285
448
|
Downloads resources defined in metadata to a local destination folder.
|
|
@@ -321,8 +484,11 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
|
|
|
321
484
|
continue
|
|
322
485
|
filtered_resources.append(r)
|
|
323
486
|
|
|
487
|
+
if metadata.get("download-compatible-files-only"):
|
|
488
|
+
filtered_resources = _filter_download_compatible_resources(filtered_resources)
|
|
489
|
+
|
|
324
490
|
if not filtered_resources:
|
|
325
|
-
click.echo("ℹ️ No
|
|
491
|
+
click.echo("ℹ️ No compatible resources to download.")
|
|
326
492
|
return []
|
|
327
493
|
|
|
328
494
|
click.echo(f"📥 Downloading {len(filtered_resources)} resource(s) to: {dest_folder}\n")
|
|
@@ -387,10 +553,8 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
|
|
|
387
553
|
continue
|
|
388
554
|
|
|
389
555
|
# 🌐 Standard file or URL
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
file_name = os.path.basename(parsed.path)
|
|
393
|
-
dest_path = Path(dest_folder) / file_name
|
|
556
|
+
resource_urls = _resolve_resource_url_candidates(base_url, resource)
|
|
557
|
+
dest_path = _metadata_resource_path(dest_folder, resource, resource_urls[0])
|
|
394
558
|
|
|
395
559
|
if expected_sha256 and dest_path.exists() and dest_path.is_file():
|
|
396
560
|
existing_sha = _compute_sha256(dest_path)
|
|
@@ -398,9 +562,11 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
|
|
|
398
562
|
click.echo(f"♻️ Checksum mismatch for existing file '{dest_path.name}', re-downloading.")
|
|
399
563
|
dest_path.unlink()
|
|
400
564
|
|
|
401
|
-
local_path =
|
|
402
|
-
|
|
565
|
+
local_path = _download_metadata_file_resource(
|
|
566
|
+
resource=resource,
|
|
567
|
+
resource_urls=resource_urls,
|
|
403
568
|
dest_folder=dest_folder,
|
|
569
|
+
dest_path=dest_path,
|
|
404
570
|
internal=internal
|
|
405
571
|
)
|
|
406
572
|
if expected_sha256:
|
|
@@ -417,6 +583,28 @@ def _download_assets(metadata: dict, base_url: str, dest_folder: str, internal:
|
|
|
417
583
|
|
|
418
584
|
return local_paths
|
|
419
585
|
|
|
586
|
+
def _mark_install_script_executable(metadata: Dict, install_dir: str) -> None:
|
|
587
|
+
script = metadata.get("installation", {}).get("script", "")
|
|
588
|
+
if not isinstance(script, str):
|
|
589
|
+
return
|
|
590
|
+
|
|
591
|
+
script = script.strip()
|
|
592
|
+
if not script or any(char in script for char in "\n\r;&|`$<>"):
|
|
593
|
+
return
|
|
594
|
+
|
|
595
|
+
script_path = Path(script)
|
|
596
|
+
if not script_path.is_absolute():
|
|
597
|
+
script_path = Path(install_dir) / script_path
|
|
598
|
+
try:
|
|
599
|
+
resolved = script_path.resolve()
|
|
600
|
+
install_root = Path(install_dir).resolve()
|
|
601
|
+
if resolved != install_root and install_root not in resolved.parents:
|
|
602
|
+
return
|
|
603
|
+
if resolved.is_file():
|
|
604
|
+
resolved.chmod(resolved.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
605
|
+
except OSError:
|
|
606
|
+
return
|
|
607
|
+
|
|
420
608
|
def selectable_resource_handler(metadata):
|
|
421
609
|
"""
|
|
422
610
|
Allow user to select one or more opt-in resources to download.
|
|
@@ -1222,8 +1410,10 @@ def install_from_metadata(metadata_url: str, internal: bool, install_dir: str =
|
|
|
1222
1410
|
if _is_platform_compatible(metadata, force) or force:
|
|
1223
1411
|
local_paths = _download_assets(metadata, metadata_url, install_dir, internal, tag=tag)
|
|
1224
1412
|
if len(local_paths) > 0:
|
|
1413
|
+
_mark_install_script_executable(metadata, install_dir)
|
|
1225
1414
|
_combine_multipart_files(install_dir, local_paths=local_paths)
|
|
1226
1415
|
_extract_archives_in_folder(install_dir, local_paths)
|
|
1416
|
+
_mark_install_script_executable(metadata, install_dir)
|
|
1227
1417
|
_run_installation_script(metadata=metadata, extract_path=install_dir)
|
|
1228
1418
|
|
|
1229
1419
|
except Exception as e:
|
|
@@ -242,6 +242,7 @@ def build_metadata(
|
|
|
242
242
|
install_script: str = "",
|
|
243
243
|
selectables: Optional[str] = None,
|
|
244
244
|
exclude: Optional[Sequence[str]] = None,
|
|
245
|
+
download_compatible_files_only: bool = False,
|
|
245
246
|
) -> Dict:
|
|
246
247
|
artifacts_folder = artifacts_folder.expanduser().resolve()
|
|
247
248
|
if not artifacts_folder.is_dir():
|
|
@@ -266,7 +267,7 @@ def build_metadata(
|
|
|
266
267
|
resolved_description = github_repo_description(artifacts_folder)
|
|
267
268
|
|
|
268
269
|
size_text = _format_size(total_size)
|
|
269
|
-
|
|
270
|
+
metadata = {
|
|
270
271
|
"name": name or default_package_name(artifacts_folder),
|
|
271
272
|
"version": version or default_version(artifacts_folder),
|
|
272
273
|
"release": "",
|
|
@@ -284,6 +285,9 @@ def build_metadata(
|
|
|
284
285
|
"post-message": DEFAULT_POST_MESSAGE,
|
|
285
286
|
},
|
|
286
287
|
}
|
|
288
|
+
if download_compatible_files_only:
|
|
289
|
+
metadata["download-compatible-files-only"] = True
|
|
290
|
+
return metadata
|
|
287
291
|
|
|
288
292
|
|
|
289
293
|
def metadata_filename(variant: Optional[str] = None) -> str:
|
|
@@ -103,7 +103,23 @@ def show_metadata(name, version):
|
|
|
103
103
|
"--variant",
|
|
104
104
|
help="Optional metadata variant name. Writes metadata-<variant>.json instead of metadata.json.",
|
|
105
105
|
)
|
|
106
|
-
|
|
106
|
+
@click.option(
|
|
107
|
+
"--download-compatible-files-only",
|
|
108
|
+
is_flag=True,
|
|
109
|
+
default=False,
|
|
110
|
+
help="Add download-compatible-files-only so installers download only wheel files compatible with the current platform.",
|
|
111
|
+
)
|
|
112
|
+
def build_package_metadata(
|
|
113
|
+
artifacts_folder,
|
|
114
|
+
name,
|
|
115
|
+
version,
|
|
116
|
+
description,
|
|
117
|
+
install_script,
|
|
118
|
+
selectables,
|
|
119
|
+
exclude,
|
|
120
|
+
variant,
|
|
121
|
+
download_compatible_files_only,
|
|
122
|
+
):
|
|
107
123
|
"""
|
|
108
124
|
Generate metadata.json, or metadata-<variant>.json, for sima-cli package installation.
|
|
109
125
|
"""
|
|
@@ -116,6 +132,7 @@ def build_package_metadata(artifacts_folder, name, version, description, install
|
|
|
116
132
|
install_script=install_script,
|
|
117
133
|
selectables=selectables,
|
|
118
134
|
exclude=exclude,
|
|
135
|
+
download_compatible_files_only=download_compatible_files_only,
|
|
119
136
|
)
|
|
120
137
|
output_path = write_metadata(artifacts_folder, metadata, variant=variant)
|
|
121
138
|
except ValueError as exc:
|
|
@@ -43,6 +43,8 @@ from sima_cli.sdk.utils import (
|
|
|
43
43
|
select_containers,
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
+
LINUX_NEAT_EXPORTS_PATH = Path("/etc/exports.d/neat-sdk.exports")
|
|
47
|
+
|
|
46
48
|
# ─────────────────────────────────────────────
|
|
47
49
|
# Entrypoint for setup/start
|
|
48
50
|
# ─────────────────────────────────────────────
|
|
@@ -323,6 +325,7 @@ class ExistingNfsExport:
|
|
|
323
325
|
local_export_path: str
|
|
324
326
|
client: str
|
|
325
327
|
client_allowed: bool
|
|
328
|
+
managed_by_sima: bool = False
|
|
326
329
|
|
|
327
330
|
|
|
328
331
|
@dataclass(frozen=True)
|
|
@@ -330,6 +333,7 @@ class ParsedNfsExport:
|
|
|
330
333
|
path: Path
|
|
331
334
|
client: str
|
|
332
335
|
options: Tuple[str, ...]
|
|
336
|
+
source: Optional[Path] = None
|
|
333
337
|
|
|
334
338
|
|
|
335
339
|
def _parse_export_line(line: str) -> List[ParsedNfsExport]:
|
|
@@ -373,10 +377,16 @@ def _read_linux_exports() -> List[ParsedNfsExport]:
|
|
|
373
377
|
logical_line += line[:-1] + " "
|
|
374
378
|
continue
|
|
375
379
|
logical_line += line
|
|
376
|
-
exports.extend(
|
|
380
|
+
exports.extend(
|
|
381
|
+
ParsedNfsExport(export.path, export.client, export.options, source=path)
|
|
382
|
+
for export in _parse_export_line(logical_line)
|
|
383
|
+
)
|
|
377
384
|
logical_line = ""
|
|
378
385
|
if logical_line:
|
|
379
|
-
exports.extend(
|
|
386
|
+
exports.extend(
|
|
387
|
+
ParsedNfsExport(export.path, export.client, export.options, source=path)
|
|
388
|
+
for export in _parse_export_line(logical_line)
|
|
389
|
+
)
|
|
380
390
|
except OSError:
|
|
381
391
|
continue
|
|
382
392
|
return exports
|
|
@@ -416,6 +426,12 @@ def _join_nfs_path(base: str, relative: Path) -> str:
|
|
|
416
426
|
return f"{base.rstrip('/')}/{relative_text}"
|
|
417
427
|
|
|
418
428
|
|
|
429
|
+
def _is_sima_managed_linux_export(export: ParsedNfsExport) -> bool:
|
|
430
|
+
if export.source is None:
|
|
431
|
+
return False
|
|
432
|
+
return export.source == LINUX_NEAT_EXPORTS_PATH
|
|
433
|
+
|
|
434
|
+
|
|
419
435
|
def _resolve_client_visible_export_path(workspace: Path, matching_export: ParsedNfsExport, exports: List[ParsedNfsExport]) -> str:
|
|
420
436
|
workspace_path = workspace.resolve()
|
|
421
437
|
|
|
@@ -452,12 +468,14 @@ def _detect_existing_linux_nfs_export(workspace: Path, devkit_ip: str, host_ip:
|
|
|
452
468
|
|
|
453
469
|
allowed_exports = [export for export in exports if _export_allows_client(export.client, devkit_ip)]
|
|
454
470
|
matching_export = max(allowed_exports or exports, key=lambda export: len(str(export.path.resolve())))
|
|
471
|
+
managed_by_sima = not allowed_exports and all(_is_sima_managed_linux_export(export) for export in exports)
|
|
455
472
|
return ExistingNfsExport(
|
|
456
473
|
server=host_ip,
|
|
457
474
|
export_path=_resolve_client_visible_export_path(workspace_path, matching_export, exports),
|
|
458
475
|
local_export_path=str(matching_export.path.resolve()),
|
|
459
476
|
client=matching_export.client,
|
|
460
477
|
client_allowed=bool(allowed_exports),
|
|
478
|
+
managed_by_sima=managed_by_sima,
|
|
461
479
|
)
|
|
462
480
|
|
|
463
481
|
|
|
@@ -551,10 +569,29 @@ def _setup_devkit_share(devkit_ip: str, workspace: str, selected_images: List[st
|
|
|
551
569
|
)
|
|
552
570
|
)
|
|
553
571
|
if not existing_export.client_allowed:
|
|
572
|
+
if existing_export.managed_by_sima:
|
|
573
|
+
print(
|
|
574
|
+
"ℹ️ Existing sima-cli-managed NFS export allows {}, updating it for DevKit {}.".format(
|
|
575
|
+
existing_export.client,
|
|
576
|
+
devkit_ip,
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
_print_devkit_nfs_banner(workspace, devkit_ip, host_os)
|
|
580
|
+
_configure_nfs_export(host_dir, devkit_ip, host_os, host_ip)
|
|
581
|
+
configure_linux_shared_devkit_network(devkit_ip)
|
|
582
|
+
print("✅ Host NFS export configured for workspace {} -> {}".format(workspace, devkit_ip))
|
|
583
|
+
return {
|
|
584
|
+
"devkit_ip": devkit_ip,
|
|
585
|
+
"host_ip": host_ip,
|
|
586
|
+
"workspace": workspace,
|
|
587
|
+
"host_platform": host_os,
|
|
588
|
+
"bootstrap_interactive": not noninteractive,
|
|
589
|
+
"noninteractive": noninteractive,
|
|
590
|
+
}
|
|
554
591
|
raise RuntimeError(
|
|
555
|
-
"Workspace is under an existing
|
|
592
|
+
"Workspace is under an existing unmanaged NFS export, but DevKit {} is not allowed "
|
|
556
593
|
"by the export client '{}'. Ask an admin to add an export entry that covers {} for the "
|
|
557
|
-
"DevKit IP/subnet, then rerun setup. sima-cli will not try to modify this
|
|
594
|
+
"DevKit IP/subnet, then rerun setup. sima-cli will not try to modify this unmanaged "
|
|
558
595
|
"export without permission.".format(
|
|
559
596
|
devkit_ip,
|
|
560
597
|
existing_export.client,
|
|
@@ -562,7 +599,7 @@ def _setup_devkit_share(devkit_ip: str, workspace: str, selected_images: List[st
|
|
|
562
599
|
)
|
|
563
600
|
)
|
|
564
601
|
print(
|
|
565
|
-
"ℹ️ Reusing
|
|
602
|
+
"ℹ️ Reusing existing NFS export for DevKit sync: {}:{}.".format(
|
|
566
603
|
existing_export.server,
|
|
567
604
|
existing_export.export_path,
|
|
568
605
|
)
|