systemlink-cli 1.22.0__tar.gz → 1.23.0__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.
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/PKG-INFO +1 -1
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/pyproject.toml +1 -1
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/_version.py +1 -1
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/example_loader.py +1 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/example_provisioner.py +374 -5
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/README.md +48 -5
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/_schema/schema-v1.0.json +2 -1
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-3/config.yaml +39 -5
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/SKILL.md +6 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/example-authoring.md +57 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_bootstrap.py +35 -1
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_click.py +5 -1
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/LICENSE +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/dff-editor/editor.js +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/dff-editor/index.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/__init__.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/__main__.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/alarm_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/comment_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/completion_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/config.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/config_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/dataframe_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/dff_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/example_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-1/README.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-1/config.yaml +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-2/README.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-2/config.yaml +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-3/leakage-current-results.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-3/overvoltage-results.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-3/product-xyz-compliance-report.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-3/product-xyz-specification.csv +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/demo-data-3/thermal-response-results.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/feed_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/file_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/function_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/main.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/mcp_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/mcp_reachability.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/mcp_server.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/notebook_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/platform.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/policy_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/profiles.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/rich_output.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/routine_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skill_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/commands.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/import-specs.min.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/job-debugging/overview.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/nipkg/overview.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/notebook/interfaces.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/notebook/notebook-patterns.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/notebook/overview.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/python-test/overview.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/routine-examples.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/troubleshooting.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/webapp/angular-ui-packages.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/webapp/deployment.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/webapp/layout-patterns.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/webapp/nimble-angular.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/webapp/overview.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/webapp/systemlink-services.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/references/webapp/troubleshooting.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/skills/slcli/scripts/spec_import_helper.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/spec_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/ssl_trust.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/state_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/system_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/system_query_utils.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/tag_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/templates_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/testmonitor_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/user_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/utils.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/web_editor.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/.gitignore +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/README.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/angular.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/package.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/app-routing.module.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/app.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/app.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/app.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/app.module.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/core/state/app-view-state.service.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/systemlink-context.service.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/theme-sync.service.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/webapp-home-data.service.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/app/shared/states/view-state.model.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/assets/README.md +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/index.html +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/main.ts +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/src/styles.scss +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/tsconfig.app.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/webapp_templates/angular/blank/tsconfig.json +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/workitem_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/workspace_click.py +0 -0
- {systemlink_cli-1.22.0 → systemlink_cli-1.23.0}/slcli/workspace_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "systemlink-cli"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.23.0"
|
|
4
4
|
description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
|
|
5
5
|
authors = ["Fred Visser <fred.visser@emerson.com>"]
|
|
6
6
|
packages = [{ include = "slcli" }]
|
|
@@ -7,6 +7,8 @@ Supports dry-run mode for validation without creating resources.
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import json as json_module
|
|
10
|
+
import re
|
|
11
|
+
import tempfile
|
|
10
12
|
import urllib.parse
|
|
11
13
|
from dataclasses import dataclass
|
|
12
14
|
from enum import Enum
|
|
@@ -16,7 +18,8 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
16
18
|
import click
|
|
17
19
|
import requests
|
|
18
20
|
|
|
19
|
-
from .utils import get_base_url, get_headers, make_api_request
|
|
21
|
+
from .utils import get_base_url, get_headers, get_ssl_verify, make_api_request, sanitize_filename
|
|
22
|
+
from .webapp_click import pack_folder_to_nipkg
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
class ProvisioningAction(Enum):
|
|
@@ -216,6 +219,7 @@ class ExampleProvisioner:
|
|
|
216
219
|
"tag": self._delete_tag,
|
|
217
220
|
"specification": self._delete_specification,
|
|
218
221
|
"feed": self._delete_feed,
|
|
222
|
+
"package": self._delete_package,
|
|
219
223
|
"alarm": self._delete_alarm,
|
|
220
224
|
}
|
|
221
225
|
delete_fn = delete_map.get(rtype)
|
|
@@ -298,6 +302,7 @@ class ExampleProvisioner:
|
|
|
298
302
|
"tag": self._create_tag,
|
|
299
303
|
"specification": self._create_specification,
|
|
300
304
|
"feed": self._create_feed,
|
|
305
|
+
"package": self._create_package,
|
|
301
306
|
"alarm": self._create_alarm,
|
|
302
307
|
}
|
|
303
308
|
|
|
@@ -374,6 +379,8 @@ class ExampleProvisioner:
|
|
|
374
379
|
str(props_with_name.get("platform", "")) or None,
|
|
375
380
|
ownership_marker=self._resource_ownership_marker(props_with_name),
|
|
376
381
|
)
|
|
382
|
+
elif rtype == "package":
|
|
383
|
+
existing_id = self._get_package_by_identity(props_with_name)
|
|
377
384
|
elif rtype == "alarm":
|
|
378
385
|
alarm_id = str(
|
|
379
386
|
props_with_name.get("alarm_id", props_with_name.get("alarmId", rname))
|
|
@@ -406,6 +413,18 @@ class ExampleProvisioner:
|
|
|
406
413
|
server_id=str(existing_id),
|
|
407
414
|
error=str(exc),
|
|
408
415
|
)
|
|
416
|
+
elif rtype == "state":
|
|
417
|
+
try:
|
|
418
|
+
self._update_state(str(existing_id), props_with_name)
|
|
419
|
+
except Exception as exc:
|
|
420
|
+
return ProvisioningResult(
|
|
421
|
+
id_reference=rid,
|
|
422
|
+
resource_type=rtype,
|
|
423
|
+
resource_name=rname,
|
|
424
|
+
action=ProvisioningAction.FAILED,
|
|
425
|
+
server_id=str(existing_id),
|
|
426
|
+
error=str(exc),
|
|
427
|
+
)
|
|
409
428
|
# Resource already exists, skip creation
|
|
410
429
|
return ProvisioningResult(
|
|
411
430
|
id_reference=rid,
|
|
@@ -514,6 +533,27 @@ class ExampleProvisioner:
|
|
|
514
533
|
product_id = self._get_product_by_name(str(referenced_resource.get("name", "")))
|
|
515
534
|
if product_id:
|
|
516
535
|
resolved[key] = product_id
|
|
536
|
+
|
|
537
|
+
for key in ("feed_id", "feedId"):
|
|
538
|
+
value = resolved.get(key)
|
|
539
|
+
if not isinstance(value, str) or not value.startswith("${") or not value.endswith("}"):
|
|
540
|
+
continue
|
|
541
|
+
reference = value[2:-1]
|
|
542
|
+
referenced_resource = resources_by_reference.get(reference)
|
|
543
|
+
if not referenced_resource or referenced_resource.get("type") != "feed":
|
|
544
|
+
continue
|
|
545
|
+
feed_properties = referenced_resource.get("properties", {})
|
|
546
|
+
if not isinstance(feed_properties, dict):
|
|
547
|
+
feed_properties = {}
|
|
548
|
+
feed_properties = dict(feed_properties)
|
|
549
|
+
feed_properties["name"] = referenced_resource.get("name", "")
|
|
550
|
+
feed_id = self._get_feed_by_name(
|
|
551
|
+
str(feed_properties["name"]),
|
|
552
|
+
str(feed_properties.get("platform", "")) or None,
|
|
553
|
+
ownership_marker=self._resource_ownership_marker(feed_properties),
|
|
554
|
+
)
|
|
555
|
+
if feed_id:
|
|
556
|
+
resolved[key] = feed_id
|
|
517
557
|
return resolved
|
|
518
558
|
|
|
519
559
|
@staticmethod
|
|
@@ -539,8 +579,8 @@ class ExampleProvisioner:
|
|
|
539
579
|
return f"slcli-example:{self.example_name}"
|
|
540
580
|
return None
|
|
541
581
|
|
|
542
|
-
def
|
|
543
|
-
"""
|
|
582
|
+
def _build_state_payload(self, props: Dict[str, Any]) -> Dict[str, Any]:
|
|
583
|
+
"""Build a systems state payload, resolving fixture resource references."""
|
|
544
584
|
payload: Dict[str, Any] = {
|
|
545
585
|
"name": props.get("name", ""),
|
|
546
586
|
"distribution": props.get("distribution", ""),
|
|
@@ -551,14 +591,17 @@ class ExampleProvisioner:
|
|
|
551
591
|
|
|
552
592
|
for source_key, api_key in (
|
|
553
593
|
("description", "description"),
|
|
554
|
-
("feeds", "feeds"),
|
|
555
|
-
("packages", "packages"),
|
|
556
594
|
("system_image", "systemImage"),
|
|
557
595
|
("systemImage", "systemImage"),
|
|
558
596
|
):
|
|
559
597
|
if source_key in props:
|
|
560
598
|
payload[api_key] = props[source_key]
|
|
561
599
|
|
|
600
|
+
if "feeds" in props:
|
|
601
|
+
payload["feeds"] = self._materialize_state_feeds(props["feeds"])
|
|
602
|
+
if "packages" in props:
|
|
603
|
+
payload["packages"] = self._materialize_state_packages(props["packages"])
|
|
604
|
+
|
|
562
605
|
state_properties = props.get("properties")
|
|
563
606
|
if isinstance(state_properties, dict):
|
|
564
607
|
state_properties = dict(state_properties)
|
|
@@ -570,6 +613,112 @@ class ExampleProvisioner:
|
|
|
570
613
|
if state_properties:
|
|
571
614
|
payload["properties"] = state_properties
|
|
572
615
|
|
|
616
|
+
return payload
|
|
617
|
+
|
|
618
|
+
def _materialize_state_feeds(self, values: Any) -> Any:
|
|
619
|
+
"""Resolve feed IDs into the structured feed objects required by states."""
|
|
620
|
+
if values is None:
|
|
621
|
+
return None
|
|
622
|
+
if not isinstance(values, list):
|
|
623
|
+
raise ValueError("State feeds must be a list")
|
|
624
|
+
return [self._materialize_state_feed(value) for value in values]
|
|
625
|
+
|
|
626
|
+
def _materialize_state_feed(self, value: Any) -> Dict[str, Any]:
|
|
627
|
+
"""Resolve one state feed reference or preserve an explicit feed object."""
|
|
628
|
+
if isinstance(value, str):
|
|
629
|
+
return self._state_feed_from_id(value)
|
|
630
|
+
if not isinstance(value, dict):
|
|
631
|
+
raise ValueError("State feed entries must be references or objects")
|
|
632
|
+
|
|
633
|
+
reference = value.get("reference")
|
|
634
|
+
if reference is None:
|
|
635
|
+
return dict(value)
|
|
636
|
+
if not isinstance(reference, str) or not reference:
|
|
637
|
+
raise ValueError("State feed reference must be a non-empty string")
|
|
638
|
+
|
|
639
|
+
feed = self._state_feed_from_id(reference)
|
|
640
|
+
for key in ("name", "url", "enabled", "compressed"):
|
|
641
|
+
if key in value:
|
|
642
|
+
feed[key] = value[key]
|
|
643
|
+
return feed
|
|
644
|
+
|
|
645
|
+
def _state_feed_from_id(self, feed_id: str) -> Dict[str, Any]:
|
|
646
|
+
"""Build a state feed object from a provisioned feed ID."""
|
|
647
|
+
from .feed_click import _get_feed, _get_feed_base_url
|
|
648
|
+
|
|
649
|
+
feed_data = _get_feed(feed_id)
|
|
650
|
+
feed_name = feed_data.get("name") or feed_data.get("feedName")
|
|
651
|
+
if not feed_name:
|
|
652
|
+
raise ValueError(f"Feed {feed_id} has no name")
|
|
653
|
+
|
|
654
|
+
feed_url = (
|
|
655
|
+
feed_data.get("url")
|
|
656
|
+
or feed_data.get("directoryUri")
|
|
657
|
+
or feed_data.get("directoryURI")
|
|
658
|
+
or f"{_get_feed_base_url()}/feeds/{urllib.parse.quote(feed_id, safe='')}/files"
|
|
659
|
+
)
|
|
660
|
+
return {
|
|
661
|
+
"name": str(feed_name),
|
|
662
|
+
"url": str(feed_url),
|
|
663
|
+
"enabled": True,
|
|
664
|
+
"compressed": False,
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
def _materialize_state_packages(self, values: Any) -> Any:
|
|
668
|
+
"""Resolve package IDs into the structured package objects required by states."""
|
|
669
|
+
if values is None:
|
|
670
|
+
return None
|
|
671
|
+
if not isinstance(values, list):
|
|
672
|
+
raise ValueError("State packages must be a list")
|
|
673
|
+
return [self._materialize_state_package(value) for value in values]
|
|
674
|
+
|
|
675
|
+
def _materialize_state_package(self, value: Any) -> Dict[str, Any]:
|
|
676
|
+
"""Resolve one state package reference or preserve an explicit package object."""
|
|
677
|
+
if isinstance(value, str):
|
|
678
|
+
return self._state_package_from_id(value)
|
|
679
|
+
if not isinstance(value, dict):
|
|
680
|
+
raise ValueError("State package entries must be references or objects")
|
|
681
|
+
|
|
682
|
+
reference = value.get("reference")
|
|
683
|
+
if reference is None:
|
|
684
|
+
return dict(value)
|
|
685
|
+
if not isinstance(reference, str) or not reference:
|
|
686
|
+
raise ValueError("State package reference must be a non-empty string")
|
|
687
|
+
|
|
688
|
+
package = self._state_package_from_id(reference)
|
|
689
|
+
if "installRecommends" in value:
|
|
690
|
+
package["installRecommends"] = value["installRecommends"]
|
|
691
|
+
return package
|
|
692
|
+
|
|
693
|
+
def _state_package_from_id(self, package_id: str) -> Dict[str, Any]:
|
|
694
|
+
"""Build a state package object from a provisioned package ID."""
|
|
695
|
+
from .feed_click import _get_feed_base_url
|
|
696
|
+
|
|
697
|
+
package_url = f"{_get_feed_base_url()}/packages/{urllib.parse.quote(package_id, safe='')}"
|
|
698
|
+
resp = make_api_request("GET", package_url, payload=None, handle_errors=False)
|
|
699
|
+
data = resp.json()
|
|
700
|
+
if isinstance(data, dict) and isinstance(data.get("package"), dict):
|
|
701
|
+
data = data["package"]
|
|
702
|
+
if not isinstance(data, dict):
|
|
703
|
+
raise ValueError(f"Package {package_id} response is invalid")
|
|
704
|
+
|
|
705
|
+
metadata = data.get("metadata", {})
|
|
706
|
+
if not isinstance(metadata, dict):
|
|
707
|
+
metadata = {}
|
|
708
|
+
package_name = metadata.get("packageName") or metadata.get("name") or data.get("name")
|
|
709
|
+
version = metadata.get("version") or data.get("version")
|
|
710
|
+
if not package_name or not version:
|
|
711
|
+
raise ValueError(f"Package {package_id} has incomplete metadata")
|
|
712
|
+
return {
|
|
713
|
+
"name": str(package_name),
|
|
714
|
+
"version": str(version),
|
|
715
|
+
"installRecommends": True,
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
def _create_state(self, props: Dict[str, Any]) -> Optional[str]:
|
|
719
|
+
"""Create a systems state and return its server ID."""
|
|
720
|
+
payload = self._build_state_payload(props)
|
|
721
|
+
|
|
573
722
|
resp = make_api_request(
|
|
574
723
|
"POST",
|
|
575
724
|
f"{get_base_url()}/nisystemsstate/v1/states",
|
|
@@ -581,6 +730,15 @@ class ExampleProvisioner:
|
|
|
581
730
|
return None
|
|
582
731
|
return str(data["id"])
|
|
583
732
|
|
|
733
|
+
def _update_state(self, state_id: str, props: Dict[str, Any]) -> None:
|
|
734
|
+
"""Update an existing systems state to match the fixture configuration."""
|
|
735
|
+
make_api_request(
|
|
736
|
+
"PATCH",
|
|
737
|
+
f"{get_base_url()}/nisystemsstate/v1/states/{state_id}",
|
|
738
|
+
self._build_state_payload(props),
|
|
739
|
+
handle_errors=False,
|
|
740
|
+
)
|
|
741
|
+
|
|
584
742
|
def _get_state_by_name(
|
|
585
743
|
self, name: str, ownership_marker: Optional[str] = None
|
|
586
744
|
) -> Optional[str]:
|
|
@@ -1187,6 +1345,217 @@ class ExampleProvisioner:
|
|
|
1187
1345
|
_wait_for_job(str(job_id), timeout=int(props.get("timeout", 300)))
|
|
1188
1346
|
return feed_id
|
|
1189
1347
|
|
|
1348
|
+
def _package_identity(self, props: Dict[str, Any]) -> Tuple[str, str, str]:
|
|
1349
|
+
"""Return the feed, package name, and version used to identify a package."""
|
|
1350
|
+
feed_id = str(props.get("feed_id", props.get("feedId", "")))
|
|
1351
|
+
source = props.get("source", {})
|
|
1352
|
+
if not isinstance(source, dict):
|
|
1353
|
+
source = {}
|
|
1354
|
+
package_name = sanitize_filename(
|
|
1355
|
+
str(
|
|
1356
|
+
source.get(
|
|
1357
|
+
"package_name",
|
|
1358
|
+
source.get(
|
|
1359
|
+
"package", props.get("package_name", props.get("name", "fixture-package"))
|
|
1360
|
+
),
|
|
1361
|
+
)
|
|
1362
|
+
),
|
|
1363
|
+
"fixture-package",
|
|
1364
|
+
)
|
|
1365
|
+
version = self._safe_package_component(
|
|
1366
|
+
str(source.get("version", props.get("version", "1.0.0"))), "1.0.0"
|
|
1367
|
+
)
|
|
1368
|
+
return feed_id, package_name, version
|
|
1369
|
+
|
|
1370
|
+
def _get_package_by_identity(self, props: Dict[str, Any]) -> Optional[str]:
|
|
1371
|
+
"""Find an example package by feed, package name, and version."""
|
|
1372
|
+
from .feed_click import _list_packages
|
|
1373
|
+
|
|
1374
|
+
feed_id, package_name, version = self._package_identity(props)
|
|
1375
|
+
if not feed_id or not package_name or not version:
|
|
1376
|
+
return None
|
|
1377
|
+
|
|
1378
|
+
try:
|
|
1379
|
+
packages = _list_packages(feed_id)
|
|
1380
|
+
except Exception:
|
|
1381
|
+
return None
|
|
1382
|
+
|
|
1383
|
+
for package in packages:
|
|
1384
|
+
metadata = package.get("metadata", {})
|
|
1385
|
+
if not isinstance(metadata, dict):
|
|
1386
|
+
metadata = {}
|
|
1387
|
+
actual_name = metadata.get("packageName", metadata.get("name", package.get("name")))
|
|
1388
|
+
actual_version = metadata.get("version", package.get("version"))
|
|
1389
|
+
if str(actual_name) == package_name and str(actual_version) == version:
|
|
1390
|
+
package_id = package.get("id", package.get("packageId"))
|
|
1391
|
+
if package_id:
|
|
1392
|
+
return str(package_id)
|
|
1393
|
+
return None
|
|
1394
|
+
|
|
1395
|
+
@staticmethod
|
|
1396
|
+
def _safe_package_component(value: str, fallback: str) -> str:
|
|
1397
|
+
"""Convert a package name component into a safe folder-name component."""
|
|
1398
|
+
normalized = re.sub(r"[^A-Za-z0-9._-]+", "-", value).strip("-._")
|
|
1399
|
+
return normalized or fallback
|
|
1400
|
+
|
|
1401
|
+
def _write_dummy_package_payload(self, folder: Path, files: Any) -> None:
|
|
1402
|
+
"""Write inline dummy package files while preventing path escapes."""
|
|
1403
|
+
if files is None:
|
|
1404
|
+
files = {"README.txt": "Package created by slcli example provisioner\n"}
|
|
1405
|
+
if not isinstance(files, dict):
|
|
1406
|
+
raise ValueError("Dummy package source 'files' must be an object")
|
|
1407
|
+
|
|
1408
|
+
root = folder.resolve()
|
|
1409
|
+
for relative_name, content in files.items():
|
|
1410
|
+
if not isinstance(relative_name, str) or not relative_name.strip():
|
|
1411
|
+
raise ValueError("Dummy package file names must be non-empty strings")
|
|
1412
|
+
destination = (root / relative_name).resolve()
|
|
1413
|
+
try:
|
|
1414
|
+
destination.relative_to(root)
|
|
1415
|
+
except ValueError as exc:
|
|
1416
|
+
raise ValueError(
|
|
1417
|
+
f"Dummy package file must remain inside the package folder: {relative_name}"
|
|
1418
|
+
) from exc
|
|
1419
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
1420
|
+
if isinstance(content, str):
|
|
1421
|
+
destination.write_text(content, encoding="utf-8")
|
|
1422
|
+
elif isinstance(content, bytes):
|
|
1423
|
+
destination.write_bytes(content)
|
|
1424
|
+
else:
|
|
1425
|
+
raise ValueError(f"Dummy package content must be text or bytes: {relative_name}")
|
|
1426
|
+
|
|
1427
|
+
def _materialize_package(self, props: Dict[str, Any], output_dir: Path) -> Path:
|
|
1428
|
+
"""Create or resolve the package file described by a package resource."""
|
|
1429
|
+
source = props.get("source", {"type": "dummy"})
|
|
1430
|
+
if not isinstance(source, dict):
|
|
1431
|
+
raise ValueError("Package source must be an object")
|
|
1432
|
+
source_type = str(source.get("type", "dummy")).lower()
|
|
1433
|
+
|
|
1434
|
+
if source_type == "file":
|
|
1435
|
+
file_path = source.get("path", source.get("file"))
|
|
1436
|
+
if not isinstance(file_path, str) or not file_path:
|
|
1437
|
+
raise ValueError("File package source requires 'path'")
|
|
1438
|
+
resolved_file = self._resolve_example_file(file_path)
|
|
1439
|
+
if resolved_file is None or not resolved_file.is_file():
|
|
1440
|
+
raise FileNotFoundError(f"Package file not found: {file_path}")
|
|
1441
|
+
if resolved_file.suffix.lower() != ".nipkg":
|
|
1442
|
+
raise ValueError("Package file source must point to a .nipkg file")
|
|
1443
|
+
return resolved_file
|
|
1444
|
+
|
|
1445
|
+
if source_type == "repository":
|
|
1446
|
+
package_url = source.get("url", source.get("package_url"))
|
|
1447
|
+
if not isinstance(package_url, str) or not package_url:
|
|
1448
|
+
raise ValueError("Repository package source requires 'url'")
|
|
1449
|
+
parsed = urllib.parse.urlparse(package_url)
|
|
1450
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
1451
|
+
raise ValueError("Repository package URL must use http or https")
|
|
1452
|
+
filename = Path(parsed.path).name or "repository-package.nipkg"
|
|
1453
|
+
if not filename.lower().endswith(".nipkg"):
|
|
1454
|
+
raise ValueError("Repository package URL must point to a .nipkg file")
|
|
1455
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
1456
|
+
destination = output_dir / filename
|
|
1457
|
+
max_size_bytes = int(source.get("max_size_bytes", 1024 * 1024 * 1024))
|
|
1458
|
+
if max_size_bytes <= 0:
|
|
1459
|
+
raise ValueError("Repository package 'max_size_bytes' must be positive")
|
|
1460
|
+
response = requests.get(
|
|
1461
|
+
package_url,
|
|
1462
|
+
stream=True,
|
|
1463
|
+
timeout=int(source.get("timeout", props.get("timeout", 300))),
|
|
1464
|
+
verify=get_ssl_verify(package_url),
|
|
1465
|
+
)
|
|
1466
|
+
try:
|
|
1467
|
+
response.raise_for_status()
|
|
1468
|
+
content_length = response.headers.get("Content-Length")
|
|
1469
|
+
if content_length and int(content_length) > max_size_bytes:
|
|
1470
|
+
raise ValueError("Repository package exceeds the configured size limit")
|
|
1471
|
+
downloaded_bytes = 0
|
|
1472
|
+
with open(destination, "wb") as package_file:
|
|
1473
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
1474
|
+
if chunk:
|
|
1475
|
+
downloaded_bytes += len(chunk)
|
|
1476
|
+
if downloaded_bytes > max_size_bytes:
|
|
1477
|
+
raise ValueError(
|
|
1478
|
+
"Repository package exceeds the configured size limit"
|
|
1479
|
+
)
|
|
1480
|
+
package_file.write(chunk)
|
|
1481
|
+
except Exception:
|
|
1482
|
+
destination.unlink(missing_ok=True)
|
|
1483
|
+
raise
|
|
1484
|
+
finally:
|
|
1485
|
+
response.close()
|
|
1486
|
+
return destination
|
|
1487
|
+
|
|
1488
|
+
if source_type != "dummy":
|
|
1489
|
+
raise ValueError(f"Unsupported package source type: {source_type}")
|
|
1490
|
+
|
|
1491
|
+
package_name = sanitize_filename(
|
|
1492
|
+
str(
|
|
1493
|
+
source.get(
|
|
1494
|
+
"package_name",
|
|
1495
|
+
source.get(
|
|
1496
|
+
"package", props.get("package_name", props.get("name", "fixture-package"))
|
|
1497
|
+
),
|
|
1498
|
+
)
|
|
1499
|
+
),
|
|
1500
|
+
"fixture-package",
|
|
1501
|
+
)
|
|
1502
|
+
version = self._safe_package_component(
|
|
1503
|
+
str(source.get("version", props.get("version", "1.0.0"))), "1.0.0"
|
|
1504
|
+
)
|
|
1505
|
+
architecture = self._safe_package_component(str(source.get("architecture", "all")), "all")
|
|
1506
|
+
package_folder = output_dir / f"{package_name}_{version}_{architecture}"
|
|
1507
|
+
package_folder.mkdir(parents=True, exist_ok=True)
|
|
1508
|
+
self._write_dummy_package_payload(package_folder, source.get("files"))
|
|
1509
|
+
return pack_folder_to_nipkg(package_folder)
|
|
1510
|
+
|
|
1511
|
+
def _create_package(self, props: Dict[str, Any]) -> Optional[str]:
|
|
1512
|
+
"""Create a package file and upload it to an existing feed."""
|
|
1513
|
+
from .feed_click import _list_packages, _upload_package, _wait_for_job
|
|
1514
|
+
|
|
1515
|
+
feed_id = str(props.get("feed_id", props.get("feedId", "")))
|
|
1516
|
+
if not feed_id:
|
|
1517
|
+
return None
|
|
1518
|
+
|
|
1519
|
+
with tempfile.TemporaryDirectory(prefix="slcli-example-package-") as temp_dir:
|
|
1520
|
+
package_path = self._materialize_package(props, Path(temp_dir))
|
|
1521
|
+
upload_result = _upload_package(
|
|
1522
|
+
feed_id,
|
|
1523
|
+
str(package_path),
|
|
1524
|
+
overwrite=bool(props.get("overwrite", False)),
|
|
1525
|
+
)
|
|
1526
|
+
job_id = upload_result.get("jobId", upload_result.get("job", {}).get("id", ""))
|
|
1527
|
+
package_id = upload_result.get("id", upload_result.get("packageId", ""))
|
|
1528
|
+
if job_id:
|
|
1529
|
+
completed_job = _wait_for_job(
|
|
1530
|
+
str(job_id),
|
|
1531
|
+
timeout=int(props.get("timeout", 300)),
|
|
1532
|
+
feed_id=feed_id,
|
|
1533
|
+
)
|
|
1534
|
+
package_id = completed_job.get("resourceId", package_id)
|
|
1535
|
+
if not package_id:
|
|
1536
|
+
package_id = self._get_package_by_identity(props)
|
|
1537
|
+
if not package_id:
|
|
1538
|
+
for package in _list_packages(feed_id):
|
|
1539
|
+
if Path(str(package.get("fileName", ""))).name == package_path.name:
|
|
1540
|
+
package_id = package.get("id", package.get("packageId", ""))
|
|
1541
|
+
break
|
|
1542
|
+
if not package_id:
|
|
1543
|
+
return None
|
|
1544
|
+
self._last_resource_details = {"file": package_path.name, "feed_id": feed_id}
|
|
1545
|
+
return str(package_id)
|
|
1546
|
+
|
|
1547
|
+
def _delete_package(self, props: Dict[str, Any]) -> Optional[str]:
|
|
1548
|
+
"""Delete an example package by its feed identity."""
|
|
1549
|
+
from .feed_click import _delete_package as delete_package, _wait_for_job
|
|
1550
|
+
|
|
1551
|
+
package_id = self._get_package_by_identity(props)
|
|
1552
|
+
if not package_id:
|
|
1553
|
+
return None
|
|
1554
|
+
job_id = delete_package(package_id)
|
|
1555
|
+
if job_id:
|
|
1556
|
+
_wait_for_job(str(job_id), timeout=int(props.get("timeout", 300)))
|
|
1557
|
+
return package_id
|
|
1558
|
+
|
|
1190
1559
|
# --- Create methods (real API calls) ---
|
|
1191
1560
|
def _create_location(self, props: Dict[str, Any]) -> str:
|
|
1192
1561
|
"""Create location via /nilocation/v1/locations API and return server ID."""
|
|
@@ -46,10 +46,9 @@ discover through visualization:
|
|
|
46
46
|
|
|
47
47
|
Phase-one fixture data for validating Systems and Products queries. It
|
|
48
48
|
provisions the resource families supported by the generic example provisioner:
|
|
49
|
-
1 deployment state
|
|
50
|
-
and 1 specification (metadata only).
|
|
51
|
-
jobs,
|
|
52
|
-
evidence, and workspace lifecycle.
|
|
49
|
+
1 deployment state populated with that feed and both package versions, 1 tag
|
|
50
|
+
with 12 history samples, 3 active alarms, and 1 specification (metadata only).
|
|
51
|
+
It reports unsupported jobs, specification evidence, and workspace lifecycle.
|
|
53
52
|
Because those capabilities are required by the full Nigel acceptance matrix,
|
|
54
53
|
the install intentionally exits nonzero after emitting its JSON manifest.
|
|
55
54
|
|
|
@@ -183,12 +182,55 @@ cleanup:
|
|
|
183
182
|
- `data_table` - DataFrame table schema
|
|
184
183
|
- `file` - Uploaded supporting file
|
|
185
184
|
- `notebook` - Uploaded Jupyter notebook
|
|
186
|
-
- `feed` - Package feed metadata
|
|
185
|
+
- `feed` - Package feed metadata
|
|
186
|
+
- `package` - Package uploaded to a previously created feed
|
|
187
187
|
- `state` - Systems deployment state
|
|
188
188
|
- `tag` - Workspace-scoped tag metadata
|
|
189
189
|
- `specification` - Product specification metadata
|
|
190
190
|
- `alarm` - Active alarm instance and transition
|
|
191
191
|
|
|
192
|
+
### Package Resources
|
|
193
|
+
|
|
194
|
+
Package resources must follow their feed in the resource list so that the feed
|
|
195
|
+
reference can be resolved:
|
|
196
|
+
|
|
197
|
+
```yaml
|
|
198
|
+
- type: "feed"
|
|
199
|
+
name: "Fixture Windows Feed"
|
|
200
|
+
properties:
|
|
201
|
+
platform: "windows"
|
|
202
|
+
id_reference: "feed_fixture"
|
|
203
|
+
|
|
204
|
+
- type: "package"
|
|
205
|
+
name: "Fixture package"
|
|
206
|
+
properties:
|
|
207
|
+
feed_id: "${feed_fixture}"
|
|
208
|
+
source:
|
|
209
|
+
type: "dummy"
|
|
210
|
+
package_name: "fixture-package"
|
|
211
|
+
version: "1.0.0"
|
|
212
|
+
architecture: "all"
|
|
213
|
+
files:
|
|
214
|
+
README.txt: "Package payload created for fixture validation.\n"
|
|
215
|
+
id_reference: "package_fixture"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Supported package sources are:
|
|
219
|
+
|
|
220
|
+
- `dummy` (default): creates a deterministic minimal `.nipkg` from inline
|
|
221
|
+
`files` and uploads it to the feed.
|
|
222
|
+
- `file`: uploads a fixture-relative `.nipkg` from `source.path`. The path is
|
|
223
|
+
contained within the directory containing `config.yaml`.
|
|
224
|
+
- `repository`: downloads a direct HTTP(S) URL ending in `.nipkg`, then uploads
|
|
225
|
+
it. Use a URL obtained from the repository/feed service; the repository
|
|
226
|
+
catalog is not treated as a package-byte endpoint. Downloads are streamed and
|
|
227
|
+
TLS verification remains enabled.
|
|
228
|
+
|
|
229
|
+
`source.package_name` and `source.version` identify a package for duplicate
|
|
230
|
+
detection across all source modes. Dummy packages default to the resource name
|
|
231
|
+
and version `1.0.0`; declare both values for local or repository packages when
|
|
232
|
+
they cannot be inferred from the resource name.
|
|
233
|
+
|
|
192
234
|
## Notes
|
|
193
235
|
|
|
194
236
|
- Examples are versioned with the `format_version` field (currently 1.0)
|
|
@@ -197,6 +239,7 @@ cleanup:
|
|
|
197
239
|
consume its custom order or confirmation settings.
|
|
198
240
|
- References use `${id_reference}` syntax for interpolation
|
|
199
241
|
- Resources are provisioned in list order, so referenced resources must appear first
|
|
242
|
+
- Package resources are provisioned after their feed and are deleted before it
|
|
200
243
|
- All resources created by an example are tagged with the example name for safe deletion
|
|
201
244
|
- The JSON Schema is the authoring reference; runtime validation currently checks only a subset of its types and patterns
|
|
202
245
|
- Examples with `install_manifest: true` emit grouped resource actions and a
|
|
@@ -179,6 +179,7 @@
|
|
|
179
179
|
"tag",
|
|
180
180
|
"specification",
|
|
181
181
|
"feed",
|
|
182
|
+
"package",
|
|
182
183
|
"alarm"
|
|
183
184
|
],
|
|
184
185
|
"description": "SLE resource type"
|
|
@@ -189,7 +190,7 @@
|
|
|
189
190
|
},
|
|
190
191
|
"properties": {
|
|
191
192
|
"type": "object",
|
|
192
|
-
"description": "Resource-specific properties (API fields)"
|
|
193
|
+
"description": "Resource-specific properties (API fields). For package resources, use feed_id and source with type dummy, file, or repository."
|
|
193
194
|
},
|
|
194
195
|
"id_reference": {
|
|
195
196
|
"type": "string",
|
|
@@ -8,14 +8,15 @@ description: |
|
|
|
8
8
|
|
|
9
9
|
This release provisions the resource families supported by the generic
|
|
10
10
|
example provisioner: products, virtual systems, DUT and instrument assets,
|
|
11
|
-
test results with steps, a deployment state, feed and
|
|
12
|
-
specification condition metadata, populated DataFrame
|
|
11
|
+
test results with steps, a deployment state, feed and package metadata,
|
|
12
|
+
tag metadata/history, specification condition metadata, populated DataFrame
|
|
13
|
+
tables, and supporting files.
|
|
13
14
|
Unsupported capabilities are declared below and make the install exit with a
|
|
14
15
|
nonzero status. The installer must not present this phase-one fixture as the
|
|
15
16
|
complete 14-example acceptance dataset.
|
|
16
17
|
author: "NI"
|
|
17
18
|
created_date: "2026-08-05"
|
|
18
|
-
updated_date: "2026-08-
|
|
19
|
+
updated_date: "2026-08-11"
|
|
19
20
|
tags:
|
|
20
21
|
- "nigel"
|
|
21
22
|
- "query-fixture"
|
|
@@ -38,8 +39,6 @@ validation:
|
|
|
38
39
|
- "workspace lifecycle: example install requires an existing workspace and does not create or delete it"
|
|
39
40
|
- "system package inventory: virtual-system creation does not seed enumerable packages or versions"
|
|
40
41
|
- "system jobs: the generic example provisioner has no queued/running/failed job seed path"
|
|
41
|
-
- "feed packages: feed creation and package upload are asynchronous CLI workflows, not fixture resources"
|
|
42
|
-
- "deployment state package inventory: state creation does not seed package inventory"
|
|
43
42
|
- "specification condition evidence: /nispec/v1 stores expected conditions only; it has no outcome or test-result evidence API"
|
|
44
43
|
- "system-to-asset query projection: location.minionId is provisioned, but the accepted CLI/Nigel retrieval path is not verified"
|
|
45
44
|
- "result-to-instrument query projection: asset ID and channel fields are provisioned, but the accepted CLI/Nigel retrieval path is not verified"
|
|
@@ -73,12 +72,47 @@ resources:
|
|
|
73
72
|
id_reference: "feed_nigel_windows"
|
|
74
73
|
tags: ["demo-data-3"]
|
|
75
74
|
|
|
75
|
+
- type: "package"
|
|
76
|
+
name: "Nigel Fixture Agent"
|
|
77
|
+
properties:
|
|
78
|
+
feed_id: "${feed_nigel_windows}"
|
|
79
|
+
source:
|
|
80
|
+
type: "dummy"
|
|
81
|
+
package_name: "nigel-fixture-agent"
|
|
82
|
+
version: "1.0.0"
|
|
83
|
+
architecture: "all"
|
|
84
|
+
files:
|
|
85
|
+
README.txt: "Deterministic fixture package for Systems query validation.\n"
|
|
86
|
+
package-info.txt: "nigel-fixture-agent 1.0.0\n"
|
|
87
|
+
id_reference: "package_nigel_agent"
|
|
88
|
+
tags: ["demo-data-3"]
|
|
89
|
+
|
|
90
|
+
- type: "package"
|
|
91
|
+
name: "Nigel Fixture Agent Update"
|
|
92
|
+
properties:
|
|
93
|
+
feed_id: "${feed_nigel_windows}"
|
|
94
|
+
source:
|
|
95
|
+
type: "dummy"
|
|
96
|
+
package_name: "nigel-fixture-agent"
|
|
97
|
+
version: "1.1.0"
|
|
98
|
+
architecture: "all"
|
|
99
|
+
files:
|
|
100
|
+
README.txt: "Deterministic fixture package update for Systems query validation.\n"
|
|
101
|
+
package-info.txt: "nigel-fixture-agent 1.1.0\n"
|
|
102
|
+
id_reference: "package_nigel_agent_update"
|
|
103
|
+
tags: ["demo-data-3"]
|
|
104
|
+
|
|
76
105
|
- type: "state"
|
|
77
106
|
name: "Nigel Fixture Windows State"
|
|
78
107
|
properties:
|
|
79
108
|
description: "Windows deployment state for query fixtures"
|
|
80
109
|
distribution: "WINDOWS"
|
|
81
110
|
architecture: "X64"
|
|
111
|
+
feeds:
|
|
112
|
+
- "${feed_nigel_windows}"
|
|
113
|
+
packages:
|
|
114
|
+
- "${package_nigel_agent}"
|
|
115
|
+
- "${package_nigel_agent_update}"
|
|
82
116
|
properties:
|
|
83
117
|
fixture: "demo-data-3"
|
|
84
118
|
id_reference: "state_nigel_windows"
|