systemlink-cli 1.17.4__tar.gz → 1.19.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.17.4 → systemlink_cli-1.19.0}/PKG-INFO +1 -1
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/pyproject.toml +1 -1
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/_version.py +1 -1
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/config_click.py +200 -3
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/main.py +23 -4
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/mcp_server.py +19 -4
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/platform.py +28 -4
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/commands.md +1 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/systemlink-services.md +40 -37
- systemlink_cli-1.19.0/slcli/ssl_trust.py +376 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/tag_click.py +246 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/utils.py +72 -38
- systemlink_cli-1.17.4/slcli/ssl_trust.py +0 -93
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/LICENSE +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/dff-editor/editor.js +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/dff-editor/index.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/__init__.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/__main__.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/comment_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/completion_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/config.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/dataframe_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/dff_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/example_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/example_loader.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/example_provisioner.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/README.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/_schema/schema-v1.0.json +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/demo-complete-workflow/README.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/demo-test-plans/README.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/demo-test-plans/config.yaml +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/feed_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/file_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/function_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/mcp_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/mcp_reachability.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/notebook_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/policy_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/profiles.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/rich_output.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/routine_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skill_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/SKILL.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/datasheet-workflow.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/import-specs.min.json +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/job-debugging/overview.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/nipkg/overview.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/notebook/interfaces.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/notebook/notebook-patterns.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/notebook/overview.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/python-test/overview.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/routine-examples.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/troubleshooting.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/angular-ui-packages.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/deployment.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/layout-patterns.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/nimble-angular.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/overview.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/references/webapp/troubleshooting.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/skills/slcli/scripts/spec_import_helper.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/spec_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/state_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/system_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/system_query_utils.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/templates_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/testmonitor_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/user_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/web_editor.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_bootstrap.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/.gitignore +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/README.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/angular.json +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/package.json +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app-routing.module.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/app.module.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/layout/app-shell.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/state/app-view-state.service.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/systemlink-context.service.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/theme-sync.service.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/core/systemlink/webapp-home-data.service.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/assets/assets-page.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/datasets/datasets-page.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/home/home-page.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/master-detail/master-detail-page.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/operations/operations-page.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/features/settings/settings-page.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/empty-state.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/error-banner.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/components/loading-state.component.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/app/shared/states/view-state.model.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/assets/README.md +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/index.html +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/main.ts +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/src/styles.scss +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/tsconfig.app.json +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/webapp_templates/angular/blank/tsconfig.json +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/workitem_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.0}/slcli/workspace_click.py +0 -0
- {systemlink_cli-1.17.4 → systemlink_cli-1.19.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.19.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" }]
|
|
@@ -4,8 +4,9 @@ import getpass
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
|
+
import ssl
|
|
7
8
|
import sys
|
|
8
|
-
from typing import Any, Optional
|
|
9
|
+
from typing import Any, NoReturn, Optional
|
|
9
10
|
from urllib.parse import urlparse
|
|
10
11
|
|
|
11
12
|
import click
|
|
@@ -18,8 +19,15 @@ from .platform import (
|
|
|
18
19
|
)
|
|
19
20
|
from .profiles import ProfileConfig, Profile, check_config_file_permissions
|
|
20
21
|
from .rich_output import render_table
|
|
22
|
+
from .ssl_trust import (
|
|
23
|
+
get_managed_trust_records,
|
|
24
|
+
get_ssl_server_origin,
|
|
25
|
+
inspect_server_certificate,
|
|
26
|
+
remove_managed_trust,
|
|
27
|
+
save_managed_certificate,
|
|
28
|
+
)
|
|
21
29
|
from .table_utils import output_formatted_list
|
|
22
|
-
from .utils import ExitCodes
|
|
30
|
+
from .utils import ExitCodes, get_base_url
|
|
23
31
|
|
|
24
32
|
API_KEY_LENGTH = 42
|
|
25
33
|
API_KEY_PATTERN = re.compile(rf"^[A-Za-z0-9_-]{{{API_KEY_LENGTH}}}$")
|
|
@@ -30,7 +38,7 @@ ENV_OVERRIDE_FIELDS = (
|
|
|
30
38
|
)
|
|
31
39
|
|
|
32
40
|
|
|
33
|
-
def _exit_with_validation_error(message: str, exit_code: int = ExitCodes.INVALID_INPUT) ->
|
|
41
|
+
def _exit_with_validation_error(message: str, exit_code: int = ExitCodes.INVALID_INPUT) -> NoReturn:
|
|
34
42
|
"""Exit the command with a consistent validation message."""
|
|
35
43
|
click.echo(f"✗ {message}", err=True)
|
|
36
44
|
sys.exit(exit_code)
|
|
@@ -100,6 +108,91 @@ def _all_service_probes_unauthorized(services: dict[str, str]) -> bool:
|
|
|
100
108
|
return bool(services) and all(status == "unauthorized" for status in services.values())
|
|
101
109
|
|
|
102
110
|
|
|
111
|
+
def _normalize_fingerprint(fingerprint: str) -> str:
|
|
112
|
+
"""Normalize a SHA-256 certificate fingerprint supplied by a user."""
|
|
113
|
+
normalized = fingerprint.replace(":", "").replace(" ", "").strip().upper()
|
|
114
|
+
if len(normalized) != 64 or any(
|
|
115
|
+
character not in "0123456789ABCDEF" for character in normalized
|
|
116
|
+
):
|
|
117
|
+
_exit_with_validation_error("Certificate fingerprint must be a 64-character SHA-256 value.")
|
|
118
|
+
return normalized
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _show_certificate_warning(certificate: dict[str, Any]) -> None:
|
|
122
|
+
"""Display the identity of a certificate before it is trusted."""
|
|
123
|
+
click.echo("\n⚠️ TLS certificate verification failed.", err=True)
|
|
124
|
+
click.echo(f" Server: {certificate.get('origin', 'unknown')}", err=True)
|
|
125
|
+
click.echo(f" Subject: {certificate.get('subject', 'unknown')}", err=True)
|
|
126
|
+
click.echo(f" Issuer: {certificate.get('issuer', 'unknown')}", err=True)
|
|
127
|
+
click.echo(f" SHA-256: {certificate.get('fingerprint', 'unknown')}", err=True)
|
|
128
|
+
click.echo(f" Self-signed: {'yes' if certificate.get('self-signed') else 'no'}", err=True)
|
|
129
|
+
click.echo(
|
|
130
|
+
f" Valid: {certificate.get('not-before', 'unknown')} to "
|
|
131
|
+
f"{certificate.get('not-after', 'unknown')}",
|
|
132
|
+
err=True,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _trust_certificate_if_requested(
|
|
137
|
+
url: str, api_key: str, status: dict[str, Any], trust_fingerprint: Optional[str]
|
|
138
|
+
) -> dict[str, Any]:
|
|
139
|
+
"""Prompt for or apply certificate trust after a verification failure."""
|
|
140
|
+
certificate_details = status.get("certificate")
|
|
141
|
+
if not isinstance(certificate_details, dict):
|
|
142
|
+
_exit_with_validation_error(
|
|
143
|
+
"TLS certificate verification failed, but the server certificate could not be "
|
|
144
|
+
"inspected. Profile was not saved.",
|
|
145
|
+
ExitCodes.NETWORK_ERROR,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
_show_certificate_warning(certificate_details)
|
|
149
|
+
expected_fingerprint = _normalize_fingerprint(trust_fingerprint) if trust_fingerprint else None
|
|
150
|
+
actual_fingerprint = str(certificate_details.get("fingerprint", "")).upper()
|
|
151
|
+
if expected_fingerprint:
|
|
152
|
+
if expected_fingerprint != actual_fingerprint:
|
|
153
|
+
_exit_with_validation_error(
|
|
154
|
+
"The server certificate fingerprint does not match --trust-fingerprint. "
|
|
155
|
+
"Profile was not saved.",
|
|
156
|
+
ExitCodes.INVALID_INPUT,
|
|
157
|
+
)
|
|
158
|
+
elif not click.confirm("Trust this certificate for this server?", default=False):
|
|
159
|
+
_exit_with_validation_error("Certificate was not trusted. Profile was not saved.")
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
certificate = inspect_server_certificate(url)
|
|
163
|
+
except (OSError, ValueError, ssl.SSLError) as exc:
|
|
164
|
+
_exit_with_validation_error(
|
|
165
|
+
f"Could not inspect the server certificate: {exc}. Profile was not saved.",
|
|
166
|
+
ExitCodes.NETWORK_ERROR,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if certificate.fingerprint != actual_fingerprint:
|
|
170
|
+
_exit_with_validation_error(
|
|
171
|
+
"The server certificate changed during approval. Profile was not saved.",
|
|
172
|
+
ExitCodes.NETWORK_ERROR,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
save_managed_certificate(certificate)
|
|
177
|
+
retry_status = check_service_status(url, api_key)
|
|
178
|
+
except (OSError, ValueError) as exc:
|
|
179
|
+
_exit_with_validation_error(
|
|
180
|
+
f"Could not save the trusted certificate: {exc}. Profile was not saved.",
|
|
181
|
+
ExitCodes.GENERAL_ERROR,
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if not retry_status.get("server_reachable") or retry_status.get("certificate_error"):
|
|
185
|
+
remove_managed_trust(url)
|
|
186
|
+
_exit_with_validation_error(
|
|
187
|
+
"The server could not be verified after trusting its certificate. "
|
|
188
|
+
"Profile was not saved.",
|
|
189
|
+
ExitCodes.NETWORK_ERROR,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
click.echo(" Certificate: ✓ Trusted for this server")
|
|
193
|
+
return retry_status
|
|
194
|
+
|
|
195
|
+
|
|
103
196
|
def _add_profile_impl(
|
|
104
197
|
profile: Optional[str],
|
|
105
198
|
url: Optional[str],
|
|
@@ -108,6 +201,7 @@ def _add_profile_impl(
|
|
|
108
201
|
workspace: Optional[str],
|
|
109
202
|
set_current: bool,
|
|
110
203
|
readonly: bool,
|
|
204
|
+
trust_fingerprint: Optional[str] = None,
|
|
111
205
|
) -> None:
|
|
112
206
|
"""Shared implementation for add-profile and login commands.
|
|
113
207
|
|
|
@@ -122,6 +216,7 @@ def _add_profile_impl(
|
|
|
122
216
|
workspace: Default workspace for this profile
|
|
123
217
|
set_current: Whether to set as the current profile
|
|
124
218
|
readonly: Whether to enable readonly mode
|
|
219
|
+
trust_fingerprint: Optional SHA-256 fingerprint for non-interactive trust approval
|
|
125
220
|
"""
|
|
126
221
|
# Get profile name
|
|
127
222
|
if not profile:
|
|
@@ -152,6 +247,10 @@ def _add_profile_impl(
|
|
|
152
247
|
# Detect platform type and check service status
|
|
153
248
|
click.echo("Checking server connectivity and services...")
|
|
154
249
|
status = check_service_status(url, api_key)
|
|
250
|
+
|
|
251
|
+
if status.get("certificate_error") and not status.get("server_reachable"):
|
|
252
|
+
status = _trust_certificate_if_requested(url, api_key, status, trust_fingerprint)
|
|
253
|
+
|
|
155
254
|
platform = status["platform"]
|
|
156
255
|
services = status.get("services", {})
|
|
157
256
|
|
|
@@ -458,6 +557,98 @@ def register_config_commands(cli: Any) -> None:
|
|
|
458
557
|
if warning:
|
|
459
558
|
click.echo(f"\n⚠️ {warning}", err=True)
|
|
460
559
|
|
|
560
|
+
@config.group(name="trust")
|
|
561
|
+
def trust() -> None:
|
|
562
|
+
"""Manage explicitly trusted server certificates."""
|
|
563
|
+
pass
|
|
564
|
+
|
|
565
|
+
@trust.command(name="list")
|
|
566
|
+
@click.option(
|
|
567
|
+
"--format",
|
|
568
|
+
"output_format",
|
|
569
|
+
type=click.Choice(["table", "json"]),
|
|
570
|
+
default="table",
|
|
571
|
+
help="Output format",
|
|
572
|
+
)
|
|
573
|
+
def list_trusted_certificates(output_format: str) -> None:
|
|
574
|
+
"""List certificates trusted by slcli."""
|
|
575
|
+
records = get_managed_trust_records()
|
|
576
|
+
if output_format == "json":
|
|
577
|
+
click.echo(json.dumps(records, indent=2))
|
|
578
|
+
return
|
|
579
|
+
if not records:
|
|
580
|
+
click.echo("No managed server certificates.")
|
|
581
|
+
return
|
|
582
|
+
|
|
583
|
+
rows = [
|
|
584
|
+
[
|
|
585
|
+
str(record.get("origin", "")),
|
|
586
|
+
str(record.get("fingerprint", "")),
|
|
587
|
+
"yes" if record.get("self-signed") else "no",
|
|
588
|
+
]
|
|
589
|
+
for record in records
|
|
590
|
+
]
|
|
591
|
+
render_table(
|
|
592
|
+
headers=["SERVER", "SHA-256", "SELF-SIGNED"],
|
|
593
|
+
column_widths=[40, 64, 12],
|
|
594
|
+
rows=rows,
|
|
595
|
+
show_total=True,
|
|
596
|
+
total_label="certificate(s)",
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
@trust.command(name="add")
|
|
600
|
+
@click.option("--url", help="HTTPS server URL (defaults to the active API URL)")
|
|
601
|
+
@click.option(
|
|
602
|
+
"--fingerprint",
|
|
603
|
+
required=True,
|
|
604
|
+
help="Expected SHA-256 fingerprint of the certificate to trust",
|
|
605
|
+
)
|
|
606
|
+
def add_trusted_certificate(url: Optional[str], fingerprint: str) -> None:
|
|
607
|
+
"""Trust a server certificate after verifying its fingerprint."""
|
|
608
|
+
server_url = url or get_base_url()
|
|
609
|
+
try:
|
|
610
|
+
expected_fingerprint = _normalize_fingerprint(fingerprint)
|
|
611
|
+
certificate = inspect_server_certificate(server_url)
|
|
612
|
+
except (OSError, ValueError, ssl.SSLError) as exc:
|
|
613
|
+
_exit_with_validation_error(f"Could not inspect the server certificate: {exc}.")
|
|
614
|
+
|
|
615
|
+
_show_certificate_warning(certificate.to_dict())
|
|
616
|
+
if certificate.fingerprint != expected_fingerprint:
|
|
617
|
+
_exit_with_validation_error(
|
|
618
|
+
"The server certificate fingerprint does not match the supplied fingerprint."
|
|
619
|
+
)
|
|
620
|
+
try:
|
|
621
|
+
path = save_managed_certificate(certificate)
|
|
622
|
+
except OSError as exc:
|
|
623
|
+
_exit_with_validation_error(f"Could not save the trusted certificate: {exc}.")
|
|
624
|
+
click.echo(f"✓ Trusted certificate for {certificate.origin}")
|
|
625
|
+
click.echo(f" Certificate file: {path}")
|
|
626
|
+
|
|
627
|
+
@trust.command(name="remove")
|
|
628
|
+
@click.option("--url", help="HTTPS server URL (defaults to the active API URL)")
|
|
629
|
+
@click.option("--force", is_flag=True, help="Skip confirmation prompt")
|
|
630
|
+
def remove_trusted_certificate(url: Optional[str], force: bool) -> None:
|
|
631
|
+
"""Remove a server certificate from the managed trust store."""
|
|
632
|
+
server_url = url or get_base_url()
|
|
633
|
+
try:
|
|
634
|
+
origin = get_ssl_server_origin(server_url)
|
|
635
|
+
except ValueError as exc:
|
|
636
|
+
_exit_with_validation_error(str(exc))
|
|
637
|
+
if not force and not click.confirm(
|
|
638
|
+
f"Remove trusted certificate for {origin}?", default=False
|
|
639
|
+
):
|
|
640
|
+
click.echo("Certificate was not removed.")
|
|
641
|
+
return
|
|
642
|
+
try:
|
|
643
|
+
removed = remove_managed_trust(server_url)
|
|
644
|
+
except (OSError, ValueError) as exc:
|
|
645
|
+
_exit_with_validation_error(f"Could not remove the trusted certificate: {exc}.")
|
|
646
|
+
if not removed:
|
|
647
|
+
_exit_with_validation_error(
|
|
648
|
+
f"No trusted certificate found for {origin}.", ExitCodes.NOT_FOUND
|
|
649
|
+
)
|
|
650
|
+
click.echo(f"✓ Removed trusted certificate for {origin}")
|
|
651
|
+
|
|
461
652
|
@config.command(name="delete")
|
|
462
653
|
@click.argument("name")
|
|
463
654
|
@click.option("--force", "-f", is_flag=True, help="Skip confirmation prompt")
|
|
@@ -558,6 +749,10 @@ def register_config_commands(cli: Any) -> None:
|
|
|
558
749
|
"publish, and disable commands)"
|
|
559
750
|
),
|
|
560
751
|
)
|
|
752
|
+
@click.option(
|
|
753
|
+
"--trust-fingerprint",
|
|
754
|
+
help="Trust a certificate after its SHA-256 fingerprint matches exactly",
|
|
755
|
+
)
|
|
561
756
|
def add_profile(
|
|
562
757
|
profile: Optional[str],
|
|
563
758
|
url: Optional[str],
|
|
@@ -566,6 +761,7 @@ def register_config_commands(cli: Any) -> None:
|
|
|
566
761
|
workspace: Optional[str],
|
|
567
762
|
set_current: bool,
|
|
568
763
|
readonly: bool,
|
|
764
|
+
trust_fingerprint: Optional[str],
|
|
569
765
|
) -> None:
|
|
570
766
|
"""Add or update a SystemLink profile.
|
|
571
767
|
|
|
@@ -588,4 +784,5 @@ def register_config_commands(cli: Any) -> None:
|
|
|
588
784
|
workspace=workspace,
|
|
589
785
|
set_current=set_current,
|
|
590
786
|
readonly=readonly,
|
|
787
|
+
trust_fingerprint=trust_fingerprint,
|
|
591
788
|
)
|
|
@@ -8,7 +8,7 @@ import ssl
|
|
|
8
8
|
import tomllib
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from types import ModuleType
|
|
11
|
-
from typing import List, Optional, Tuple
|
|
11
|
+
from typing import List, Optional, Tuple, Union
|
|
12
12
|
from urllib.parse import urlparse
|
|
13
13
|
|
|
14
14
|
import click as base_click
|
|
@@ -103,6 +103,16 @@ def _configure_rich_click_command_groups() -> None:
|
|
|
103
103
|
|
|
104
104
|
def _get_ca_source_display() -> str:
|
|
105
105
|
"""Describe the CA source used for HTTPS verification."""
|
|
106
|
+
try:
|
|
107
|
+
from .ssl_trust import get_managed_trust_path
|
|
108
|
+
from .utils import get_base_url
|
|
109
|
+
|
|
110
|
+
managed_path = get_managed_trust_path(get_base_url())
|
|
111
|
+
if managed_path is not None:
|
|
112
|
+
return f"managed-pem ({managed_path})"
|
|
113
|
+
except (OSError, ValueError):
|
|
114
|
+
pass
|
|
115
|
+
|
|
106
116
|
if OS_TRUST_INJECTED:
|
|
107
117
|
return f"system (reason={OS_TRUST_REASON})"
|
|
108
118
|
|
|
@@ -113,7 +123,7 @@ def _get_ca_source_display() -> str:
|
|
|
113
123
|
return f"certifi (reason={OS_TRUST_REASON})"
|
|
114
124
|
|
|
115
125
|
|
|
116
|
-
def _build_tls_debug_context(ssl_verify: bool) -> ssl.SSLContext:
|
|
126
|
+
def _build_tls_debug_context(ssl_verify: Union[bool, str]) -> ssl.SSLContext:
|
|
117
127
|
"""Build an SSL context aligned with the current CLI trust configuration."""
|
|
118
128
|
if not ssl_verify:
|
|
119
129
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
@@ -121,6 +131,9 @@ def _build_tls_debug_context(ssl_verify: bool) -> ssl.SSLContext:
|
|
|
121
131
|
ctx.verify_mode = ssl.CERT_NONE
|
|
122
132
|
return ctx
|
|
123
133
|
|
|
134
|
+
if isinstance(ssl_verify, str):
|
|
135
|
+
return ssl.create_default_context(cafile=ssl_verify)
|
|
136
|
+
|
|
124
137
|
verify_env = os.environ.get("REQUESTS_CA_BUNDLE") or os.environ.get("SSL_CERT_FILE")
|
|
125
138
|
if verify_env:
|
|
126
139
|
return ssl.create_default_context(cafile=verify_env)
|
|
@@ -204,7 +217,7 @@ def _get_proxy_debug_rows(api_url: str) -> List[Tuple[str, str]]:
|
|
|
204
217
|
]
|
|
205
218
|
|
|
206
219
|
|
|
207
|
-
def _probe_tls_connection(api_url: str, ssl_verify: bool) -> List[Tuple[str, str]]:
|
|
220
|
+
def _probe_tls_connection(api_url: str, ssl_verify: Union[bool, str]) -> List[Tuple[str, str]]:
|
|
208
221
|
"""Collect TLS handshake and leaf certificate details for the API host."""
|
|
209
222
|
import requests
|
|
210
223
|
|
|
@@ -268,7 +281,7 @@ def _collect_info_debug_rows(api_url: str) -> List[Tuple[str, str]]:
|
|
|
268
281
|
"""Return structured connection diagnostics for info --debug."""
|
|
269
282
|
from .utils import get_ssl_verify
|
|
270
283
|
|
|
271
|
-
ssl_verify = get_ssl_verify()
|
|
284
|
+
ssl_verify = get_ssl_verify(api_url)
|
|
272
285
|
rows = [
|
|
273
286
|
("SSL Verify", "enabled" if ssl_verify else "disabled"),
|
|
274
287
|
("CA Source", _get_ca_source_display()),
|
|
@@ -414,6 +427,10 @@ def ca_info() -> None:
|
|
|
414
427
|
"publish, and disable commands)"
|
|
415
428
|
),
|
|
416
429
|
)
|
|
430
|
+
@click.option(
|
|
431
|
+
"--trust-fingerprint",
|
|
432
|
+
help="Trust a certificate after its SHA-256 fingerprint matches exactly",
|
|
433
|
+
)
|
|
417
434
|
def login(
|
|
418
435
|
profile: Optional[str],
|
|
419
436
|
url: Optional[str],
|
|
@@ -422,6 +439,7 @@ def login(
|
|
|
422
439
|
workspace: Optional[str],
|
|
423
440
|
set_current: bool,
|
|
424
441
|
readonly: bool,
|
|
442
|
+
trust_fingerprint: Optional[str],
|
|
425
443
|
) -> None:
|
|
426
444
|
"""Create or update a SystemLink profile with credentials.
|
|
427
445
|
|
|
@@ -447,6 +465,7 @@ def login(
|
|
|
447
465
|
workspace=workspace,
|
|
448
466
|
set_current=set_current,
|
|
449
467
|
readonly=readonly,
|
|
468
|
+
trust_fingerprint=trust_fingerprint,
|
|
450
469
|
)
|
|
451
470
|
|
|
452
471
|
|
|
@@ -254,12 +254,27 @@ def query_tag_history(path: str, take: int = 100) -> str:
|
|
|
254
254
|
from .utils import get_base_url
|
|
255
255
|
|
|
256
256
|
path = _require(path, "path")
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
url = f"{get_base_url()}/nitaghistorian/v2/tags/query-history"
|
|
258
|
+
data = _post_json(
|
|
259
|
+
url,
|
|
260
|
+
{
|
|
261
|
+
"path": path,
|
|
262
|
+
"startTime": "0001-01-01T00:00:00Z",
|
|
263
|
+
"endTime": "9999-12-31T23:59:59Z",
|
|
264
|
+
"take": take,
|
|
265
|
+
"sortOrder": "DESCENDING",
|
|
266
|
+
},
|
|
267
|
+
)
|
|
260
268
|
if isinstance(data, list):
|
|
261
269
|
return _dump(data)
|
|
262
|
-
|
|
270
|
+
values = data.get("values", [])
|
|
271
|
+
value_type = data.get("type")
|
|
272
|
+
if isinstance(values, list) and isinstance(value_type, str):
|
|
273
|
+
values = [
|
|
274
|
+
{**item, "type": value_type} if isinstance(item, dict) and "type" not in item else item
|
|
275
|
+
for item in values
|
|
276
|
+
]
|
|
277
|
+
return _dump(values)
|
|
263
278
|
|
|
264
279
|
|
|
265
280
|
@server.tool()
|
|
@@ -6,6 +6,7 @@ This module provides utilities to detect and manage the target platform
|
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
|
+
import ssl
|
|
9
10
|
import sys
|
|
10
11
|
import time
|
|
11
12
|
from functools import lru_cache
|
|
@@ -15,6 +16,7 @@ import click
|
|
|
15
16
|
import keyring
|
|
16
17
|
import requests
|
|
17
18
|
|
|
19
|
+
from .ssl_trust import use_standard_ssl_context
|
|
18
20
|
from .utils import ExitCodes, get_ssl_verify
|
|
19
21
|
|
|
20
22
|
DEFAULT_SERVICE_PROBE_CACHE_TTL_SECONDS = 300
|
|
@@ -206,7 +208,7 @@ def _probe_service_status(api_url: str, api_key: str, method: str, url_path: str
|
|
|
206
208
|
"Content-Type": "application/json",
|
|
207
209
|
"User-Agent": "SystemLink-CLI/1.0 (cross-platform)",
|
|
208
210
|
}
|
|
209
|
-
ssl_verify = get_ssl_verify()
|
|
211
|
+
ssl_verify = get_ssl_verify(api_url)
|
|
210
212
|
|
|
211
213
|
try:
|
|
212
214
|
full_url = f"{api_url}{url_path}"
|
|
@@ -469,7 +471,7 @@ def get_file_query_capability(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
|
469
471
|
"Content-Type": "application/json",
|
|
470
472
|
"User-Agent": "SystemLink-CLI/1.0 (cross-platform)",
|
|
471
473
|
}
|
|
472
|
-
ssl_verify = get_ssl_verify()
|
|
474
|
+
ssl_verify = get_ssl_verify(api_url)
|
|
473
475
|
|
|
474
476
|
try:
|
|
475
477
|
search_resp = requests.post(
|
|
@@ -583,7 +585,7 @@ def get_system_query_capability(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
|
583
585
|
"Content-Type": "application/json",
|
|
584
586
|
"User-Agent": "SystemLink-CLI/1.0 (cross-platform)",
|
|
585
587
|
}
|
|
586
|
-
ssl_verify = get_ssl_verify()
|
|
588
|
+
ssl_verify = get_ssl_verify(api_url)
|
|
587
589
|
|
|
588
590
|
try:
|
|
589
591
|
search_resp = requests.post(
|
|
@@ -679,17 +681,25 @@ def check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
|
679
681
|
- platform: detected platform string (PLATFORM_SLE, PLATFORM_SLS,
|
|
680
682
|
PLATFORM_UNREACHABLE, PLATFORM_UNKNOWN)
|
|
681
683
|
"""
|
|
684
|
+
ssl_verify = get_ssl_verify(api_url)
|
|
685
|
+
with use_standard_ssl_context(ssl_verify):
|
|
686
|
+
return _check_service_status(api_url, api_key)
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
def _check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
690
|
+
"""Probe services while the caller has selected the appropriate SSL context."""
|
|
682
691
|
headers = {
|
|
683
692
|
"x-ni-api-key": api_key,
|
|
684
693
|
"Content-Type": "application/json",
|
|
685
694
|
"User-Agent": "SystemLink-CLI/1.0 (cross-platform)",
|
|
686
695
|
}
|
|
687
|
-
ssl_verify = get_ssl_verify()
|
|
696
|
+
ssl_verify = get_ssl_verify(api_url)
|
|
688
697
|
|
|
689
698
|
services: Dict[str, str] = {}
|
|
690
699
|
any_responded = False
|
|
691
700
|
any_authorized = False
|
|
692
701
|
all_unauthorized = True
|
|
702
|
+
certificate_error = False
|
|
693
703
|
|
|
694
704
|
for display_name, method, url_path in SERVICE_CHECKS:
|
|
695
705
|
try:
|
|
@@ -725,15 +735,28 @@ def check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
|
725
735
|
else:
|
|
726
736
|
services[display_name] = "error"
|
|
727
737
|
all_unauthorized = False
|
|
738
|
+
except requests.exceptions.SSLError:
|
|
739
|
+
services[display_name] = "certificate_error"
|
|
740
|
+
certificate_error = True
|
|
728
741
|
except requests.RequestException:
|
|
729
742
|
services[display_name] = "unreachable"
|
|
730
743
|
|
|
731
744
|
# Determine overall status
|
|
732
745
|
if not any_responded:
|
|
746
|
+
certificate_details: Optional[Dict[str, Any]] = None
|
|
747
|
+
if certificate_error:
|
|
748
|
+
try:
|
|
749
|
+
from .ssl_trust import inspect_server_certificate
|
|
750
|
+
|
|
751
|
+
certificate_details = inspect_server_certificate(api_url).to_dict()
|
|
752
|
+
except (OSError, ValueError, ssl.SSLError):
|
|
753
|
+
certificate_details = None
|
|
733
754
|
return {
|
|
734
755
|
"server_reachable": False,
|
|
735
756
|
"auth_valid": None,
|
|
736
757
|
"services": services,
|
|
758
|
+
"certificate_error": certificate_error,
|
|
759
|
+
"certificate": certificate_details,
|
|
737
760
|
"file_query_endpoint": None,
|
|
738
761
|
"elasticsearch_available": None,
|
|
739
762
|
"system_query_endpoint": None,
|
|
@@ -759,6 +782,7 @@ def check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
|
759
782
|
"server_reachable": True,
|
|
760
783
|
"auth_valid": auth_valid,
|
|
761
784
|
"services": services,
|
|
785
|
+
"certificate_error": certificate_error,
|
|
762
786
|
"file_query_endpoint": file_capability["file_query_endpoint"],
|
|
763
787
|
"elasticsearch_available": file_capability["elasticsearch_available"],
|
|
764
788
|
"system_query_endpoint": system_capability["system_query_endpoint"],
|
|
@@ -378,6 +378,7 @@ slcli dataframe delete <TABLE_ID>... [--yes]
|
|
|
378
378
|
slcli tag list [OPTIONS] # List tags (filter by path glob, workspace)
|
|
379
379
|
slcli tag get <TAG_PATH> [-f json] # Get tag metadata
|
|
380
380
|
slcli tag get-value <TAG_PATH> # Read current tag value
|
|
381
|
+
slcli tag history <TAG_PATH> [-w WORKSPACE] [-t TAKE] [-f json] [--graph] # Read or graph history
|
|
381
382
|
slcli tag set-value <TAG_PATH> <VALUE> # Write a tag value
|
|
382
383
|
slcli tag create --path <PATH> --data-type <TYPE> # Create a new tag
|
|
383
384
|
slcli tag update <TAG_PATH> [OPTIONS] # Update tag metadata
|