fdai-deployment-cli 0.1.2__py3-none-any.whl
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.
- fdai_deployment_cli/__about__.py +3 -0
- fdai_deployment_cli/__init__.py +5 -0
- fdai_deployment_cli/__main__.py +5 -0
- fdai_deployment_cli/acr_publish.py +585 -0
- fdai_deployment_cli/aks_catalog_review.py +137 -0
- fdai_deployment_cli/aks_historical_reconciliation.py +732 -0
- fdai_deployment_cli/aks_job_environment.py +59 -0
- fdai_deployment_cli/aks_job_execution.py +794 -0
- fdai_deployment_cli/aks_kubernetes_defaults.py +66 -0
- fdai_deployment_cli/aks_preflight.py +422 -0
- fdai_deployment_cli/aks_readiness.py +220 -0
- fdai_deployment_cli/aks_service_update.py +180 -0
- fdai_deployment_cli/aks_workload_jobs.py +281 -0
- fdai_deployment_cli/application_state_adoption.py +347 -0
- fdai_deployment_cli/azure_actor.py +61 -0
- fdai_deployment_cli/azure_naming.py +252 -0
- fdai_deployment_cli/azure_retail_prices.py +173 -0
- fdai_deployment_cli/bootstrap_approval.py +99 -0
- fdai_deployment_cli/bootstrap_channel.py +321 -0
- fdai_deployment_cli/bootstrap_executor.py +311 -0
- fdai_deployment_cli/bootstrap_plan.py +237 -0
- fdai_deployment_cli/bootstrap_reconcile.py +402 -0
- fdai_deployment_cli/bootstrap_support.py +138 -0
- fdai_deployment_cli/bootstrap_worker.py +448 -0
- fdai_deployment_cli/bundle.py +352 -0
- fdai_deployment_cli/capacity.py +143 -0
- fdai_deployment_cli/catalog_review_profile.py +306 -0
- fdai_deployment_cli/cli.py +798 -0
- fdai_deployment_cli/cli_help.py +214 -0
- fdai_deployment_cli/cli_parser.py +791 -0
- fdai_deployment_cli/cli_plan.py +450 -0
- fdai_deployment_cli/compiler.py +99 -0
- fdai_deployment_cli/console_artifact.py +218 -0
- fdai_deployment_cli/console_config.py +214 -0
- fdai_deployment_cli/console_update.py +832 -0
- fdai_deployment_cli/contracts.py +307 -0
- fdai_deployment_cli/control_package.py +140 -0
- fdai_deployment_cli/deadline_transport.py +70 -0
- fdai_deployment_cli/deployment_cost.py +131 -0
- fdai_deployment_cli/deployment_deadline.py +28 -0
- fdai_deployment_cli/deployment_kit.py +790 -0
- fdai_deployment_cli/deployment_kit_cache.py +191 -0
- fdai_deployment_cli/deployment_progress.py +648 -0
- fdai_deployment_cli/development_authority_cli.py +56 -0
- fdai_deployment_cli/development_authority_profile.py +319 -0
- fdai_deployment_cli/doctor.py +136 -0
- fdai_deployment_cli/entitlement_preflight.py +68 -0
- fdai_deployment_cli/entitlement_transport.py +49 -0
- fdai_deployment_cli/entra_bootstrap.py +579 -0
- fdai_deployment_cli/entra_control_profile.py +214 -0
- fdai_deployment_cli/entra_graph.py +349 -0
- fdai_deployment_cli/entra_profiles.py +215 -0
- fdai_deployment_cli/entra_source.py +244 -0
- fdai_deployment_cli/execution_bundle.py +478 -0
- fdai_deployment_cli/execution_copies.py +63 -0
- fdai_deployment_cli/foundation_adoption.py +438 -0
- fdai_deployment_cli/foundation_adoption_evidence.py +245 -0
- fdai_deployment_cli/foundation_adoption_host.py +157 -0
- fdai_deployment_cli/foundation_adoption_transport.py +51 -0
- fdai_deployment_cli/foundation_failure.py +72 -0
- fdai_deployment_cli/foundation_image.py +168 -0
- fdai_deployment_cli/foundation_input.py +235 -0
- fdai_deployment_cli/foundation_output.py +154 -0
- fdai_deployment_cli/foundation_plan.py +381 -0
- fdai_deployment_cli/foundation_process.py +64 -0
- fdai_deployment_cli/foundation_progress.py +231 -0
- fdai_deployment_cli/github_actions.py +171 -0
- fdai_deployment_cli/github_apply_artifact.py +259 -0
- fdai_deployment_cli/github_artifact_io.py +48 -0
- fdai_deployment_cli/github_dispatch.py +248 -0
- fdai_deployment_cli/github_plan_artifact.py +181 -0
- fdai_deployment_cli/github_provider_artifact.py +170 -0
- fdai_deployment_cli/github_transport.py +46 -0
- fdai_deployment_cli/github_workflow_values.py +312 -0
- fdai_deployment_cli/hub_image_deploy.py +354 -0
- fdai_deployment_cli/hub_image_stage.py +162 -0
- fdai_deployment_cli/hub_image_trust.py +281 -0
- fdai_deployment_cli/hub_ownership.py +161 -0
- fdai_deployment_cli/hub_workload_handoff.py +264 -0
- fdai_deployment_cli/hub_workload_handoff_inputs.py +227 -0
- fdai_deployment_cli/identity_profile.py +227 -0
- fdai_deployment_cli/image_targets.py +197 -0
- fdai_deployment_cli/installation_scope.py +280 -0
- fdai_deployment_cli/license.py +311 -0
- fdai_deployment_cli/license_issue.py +239 -0
- fdai_deployment_cli/lifecycle_authorization.py +252 -0
- fdai_deployment_cli/lifecycle_commands.py +173 -0
- fdai_deployment_cli/lifecycle_configuration.py +722 -0
- fdai_deployment_cli/lifecycle_configuration_artifact.py +45 -0
- fdai_deployment_cli/lifecycle_configuration_cli.py +324 -0
- fdai_deployment_cli/lifecycle_configuration_document.py +74 -0
- fdai_deployment_cli/lifecycle_configuration_entities.py +146 -0
- fdai_deployment_cli/lifecycle_configuration_package.py +492 -0
- fdai_deployment_cli/lifecycle_configuration_publication.py +277 -0
- fdai_deployment_cli/lifecycle_configuration_publication_cli.py +191 -0
- fdai_deployment_cli/lifecycle_configuration_sealing.py +127 -0
- fdai_deployment_cli/lifecycle_enrollment.py +45 -0
- fdai_deployment_cli/lifecycle_infrastructure_agent.py +174 -0
- fdai_deployment_cli/lifecycle_installation_binding.py +65 -0
- fdai_deployment_cli/lifecycle_native.py +247 -0
- fdai_deployment_cli/lifecycle_native_reconciliation.py +568 -0
- fdai_deployment_cli/lifecycle_native_transition.py +339 -0
- fdai_deployment_cli/lifecycle_ownership_exclusion.py +78 -0
- fdai_deployment_cli/lifecycle_plan.py +800 -0
- fdai_deployment_cli/lifecycle_promotion.py +185 -0
- fdai_deployment_cli/lifecycle_release.py +308 -0
- fdai_deployment_cli/lifecycle_self_upgrade.py +202 -0
- fdai_deployment_cli/lifecycle_upgrade_bundle.py +176 -0
- fdai_deployment_cli/notification_recipients_input.py +222 -0
- fdai_deployment_cli/observer_artifact.py +106 -0
- fdai_deployment_cli/oci_archive.py +353 -0
- fdai_deployment_cli/offline_kit.py +797 -0
- fdai_deployment_cli/offline_prepare.py +135 -0
- fdai_deployment_cli/operational_evidence_verifier_input.py +575 -0
- fdai_deployment_cli/plan_input.py +101 -0
- fdai_deployment_cli/private_output.py +192 -0
- fdai_deployment_cli/profile.py +70 -0
- fdai_deployment_cli/progress.py +183 -0
- fdai_deployment_cli/readiness.py +286 -0
- fdai_deployment_cli/run_command_receiver.py +418 -0
- fdai_deployment_cli/runtime_build.py +386 -0
- fdai_deployment_cli/runtime_profile.py +287 -0
- fdai_deployment_cli/runtime_release.py +745 -0
- fdai_deployment_cli/runtime_stage.py +82 -0
- fdai_deployment_cli/runtime_support_installation.py +215 -0
- fdai_deployment_cli/simulation.py +80 -0
- fdai_deployment_cli/source_application_inputs.py +397 -0
- fdai_deployment_cli/source_azure.py +586 -0
- fdai_deployment_cli/source_change_cli.py +107 -0
- fdai_deployment_cli/source_change_parser.py +75 -0
- fdai_deployment_cli/source_change_plan.py +181 -0
- fdai_deployment_cli/source_changed_update.py +196 -0
- fdai_deployment_cli/source_deploy.py +170 -0
- fdai_deployment_cli/source_foundation.py +174 -0
- fdai_deployment_cli/source_host_tools.py +163 -0
- fdai_deployment_cli/source_image_stage.py +400 -0
- fdai_deployment_cli/source_input.py +173 -0
- fdai_deployment_cli/source_projection.py +164 -0
- fdai_deployment_cli/source_receiver.py +76 -0
- fdai_deployment_cli/source_recovery.py +264 -0
- fdai_deployment_cli/source_runtime.py +81 -0
- fdai_deployment_cli/source_runtime_support.py +368 -0
- fdai_deployment_cli/source_service_update.py +726 -0
- fdai_deployment_cli/source_service_update_cli.py +47 -0
- fdai_deployment_cli/source_snapshot.py +178 -0
- fdai_deployment_cli/source_teardown.py +574 -0
- fdai_deployment_cli/source_terraform.py +85 -0
- fdai_deployment_cli/source_transport.py +399 -0
- fdai_deployment_cli/standalone_aks_inventory.py +558 -0
- fdai_deployment_cli/standalone_aks_job_execution.py +769 -0
- fdai_deployment_cli/standalone_aks_nodepool_guard.py +141 -0
- fdai_deployment_cli/standalone_application.py +977 -0
- fdai_deployment_cli/standalone_application_completion.py +151 -0
- fdai_deployment_cli/standalone_application_input_recovery.py +107 -0
- fdai_deployment_cli/standalone_application_recovery.py +116 -0
- fdai_deployment_cli/standalone_approval_input.py +24 -0
- fdai_deployment_cli/standalone_catalog_checkpoint.py +122 -0
- fdai_deployment_cli/standalone_catalog_review.py +251 -0
- fdai_deployment_cli/standalone_checkpoint_failure.py +127 -0
- fdai_deployment_cli/standalone_console_publish.py +174 -0
- fdai_deployment_cli/standalone_database_principals.py +41 -0
- fdai_deployment_cli/standalone_deploy.py +638 -0
- fdai_deployment_cli/standalone_development_authority.py +166 -0
- fdai_deployment_cli/standalone_foundation_adoption.py +87 -0
- fdai_deployment_cli/standalone_foundation_transition.py +767 -0
- fdai_deployment_cli/standalone_host.py +4608 -0
- fdai_deployment_cli/standalone_host_rebind.py +226 -0
- fdai_deployment_cli/standalone_host_residual.py +104 -0
- fdai_deployment_cli/standalone_host_state.py +145 -0
- fdai_deployment_cli/standalone_host_values.py +563 -0
- fdai_deployment_cli/standalone_kubernetes_tools.py +100 -0
- fdai_deployment_cli/standalone_license_installation.py +177 -0
- fdai_deployment_cli/standalone_management_egress.py +83 -0
- fdai_deployment_cli/standalone_migration_evidence.py +38 -0
- fdai_deployment_cli/standalone_operational_evidence.py +356 -0
- fdai_deployment_cli/standalone_planned_outputs.py +147 -0
- fdai_deployment_cli/standalone_private_access_probe.py +100 -0
- fdai_deployment_cli/standalone_product_profile.py +110 -0
- fdai_deployment_cli/standalone_remote_prepare.py +442 -0
- fdai_deployment_cli/standalone_residual_apply.py +540 -0
- fdai_deployment_cli/standalone_residual_successor.py +431 -0
- fdai_deployment_cli/standalone_review.py +266 -0
- fdai_deployment_cli/standalone_service_update_finalization.py +116 -0
- fdai_deployment_cli/standalone_service_update_recovery.py +277 -0
- fdai_deployment_cli/standalone_stage_targets.py +325 -0
- fdai_deployment_cli/standalone_status.py +67 -0
- fdai_deployment_cli/standalone_terraform_environment.py +237 -0
- fdai_deployment_cli/standalone_transfer_cleanup.py +237 -0
- fdai_deployment_cli/standalone_trial_activation.py +193 -0
- fdai_deployment_cli/state.py +434 -0
- fdai_deployment_cli/state_handoff.py +253 -0
- fdai_deployment_cli/status_projection.py +97 -0
- fdai_deployment_cli/support_install.py +257 -0
- fdai_deployment_cli/target.py +46 -0
- fdai_deployment_cli/trust/capabilities.json +62 -0
- fdai_deployment_cli/trust/deployment-bundle-root.pub +3 -0
- fdai_deployment_cli/trust/deployment-release-root.pub +3 -0
- fdai_deployment_cli/trust/upstream-signing-key.pub +3 -0
- fdai_deployment_cli/trust_roots.py +50 -0
- fdai_deployment_cli-0.1.2.dist-info/METADATA +112 -0
- fdai_deployment_cli-0.1.2.dist-info/RECORD +203 -0
- fdai_deployment_cli-0.1.2.dist-info/WHEEL +4 -0
- fdai_deployment_cli-0.1.2.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Prepare the managed-host application workspace from verified private inputs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from fdai_deployment_cli.application_state_adoption import ApplicationStateAdoption
|
|
11
|
+
from fdai_deployment_cli.catalog_review_profile import (
|
|
12
|
+
CatalogReviewDeploymentProfile,
|
|
13
|
+
stage_catalog_review_profile,
|
|
14
|
+
)
|
|
15
|
+
from fdai_deployment_cli.control_package import PACKAGE_ROOT, ControlPackage
|
|
16
|
+
from fdai_deployment_cli.development_authority_profile import (
|
|
17
|
+
DevelopmentAuthorityDeploymentProfile,
|
|
18
|
+
stage_development_authority_profile,
|
|
19
|
+
)
|
|
20
|
+
from fdai_deployment_cli.foundation_adoption_transport import stage_foundation_context
|
|
21
|
+
from fdai_deployment_cli.hub_image_trust import HubImageSelection, load_selection
|
|
22
|
+
from fdai_deployment_cli.notification_recipients_input import (
|
|
23
|
+
NotificationRecipientsDeploymentInput,
|
|
24
|
+
stage_notification_recipients,
|
|
25
|
+
)
|
|
26
|
+
from fdai_deployment_cli.operational_evidence_verifier_input import (
|
|
27
|
+
OperationalEvidenceVerifierDeploymentInput,
|
|
28
|
+
stage_operational_evidence_verifier_input,
|
|
29
|
+
)
|
|
30
|
+
from fdai_deployment_cli.runtime_profile import RuntimeDeploymentProfile
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def prepare_remote(
|
|
34
|
+
tunnel: Any,
|
|
35
|
+
*,
|
|
36
|
+
remote_root: str,
|
|
37
|
+
remote_archive: str,
|
|
38
|
+
archive: Path | None,
|
|
39
|
+
archive_digest: str | None,
|
|
40
|
+
handoff_path: Path,
|
|
41
|
+
remote_handoff: str,
|
|
42
|
+
entra_path: Path | None,
|
|
43
|
+
remote_entra: str | None,
|
|
44
|
+
app_work: str,
|
|
45
|
+
source_archive: Path | None = None,
|
|
46
|
+
source_archive_digest: str | None = None,
|
|
47
|
+
source_receiver: Path | None = None,
|
|
48
|
+
source_receiver_digest: str | None = None,
|
|
49
|
+
source_snapshot_digest: str | None = None,
|
|
50
|
+
source_runtime_support: Path | None = None,
|
|
51
|
+
source_runtime_support_digest: str | None = None,
|
|
52
|
+
runtime_profile: RuntimeDeploymentProfile | None = None,
|
|
53
|
+
application_state_adoption: ApplicationStateAdoption | None = None,
|
|
54
|
+
remote_adoption_state: str = "",
|
|
55
|
+
remote_adoption_models: str = "",
|
|
56
|
+
remote_adoption_descriptor: str = "",
|
|
57
|
+
timeout_seconds: int,
|
|
58
|
+
catalog_review_profile: CatalogReviewDeploymentProfile | None = None,
|
|
59
|
+
operational_evidence_verifier_input: OperationalEvidenceVerifierDeploymentInput | None = None,
|
|
60
|
+
notification_recipients: NotificationRecipientsDeploymentInput | None = None,
|
|
61
|
+
development_authority_profile: DevelopmentAuthorityDeploymentProfile | None = None,
|
|
62
|
+
clear_development_authority_profile: bool = False,
|
|
63
|
+
control_package: ControlPackage | None = None,
|
|
64
|
+
hub_selection: HubImageSelection | None = None,
|
|
65
|
+
) -> dict[str, object]:
|
|
66
|
+
"""Transfer exact inputs and invoke the value-free host preparation command.
|
|
67
|
+
|
|
68
|
+
With a verified ``control_package``, the host installs ``fdai-deployment-cli`` only from
|
|
69
|
+
that signed wheelhouse after a remote digest match; the kit still supplies every runtime
|
|
70
|
+
payload. Without it, the host installs the CLI from the kit wheels.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
selected_runtime = runtime_profile or RuntimeDeploymentProfile.create(
|
|
74
|
+
runtime_platform="container-apps",
|
|
75
|
+
database_placement="postgres-flex",
|
|
76
|
+
)
|
|
77
|
+
source_mode = source_archive is not None
|
|
78
|
+
if hub_selection is not None:
|
|
79
|
+
hub_selection = load_selection(hub_selection.path, hub_selection.digest)
|
|
80
|
+
if not source_mode:
|
|
81
|
+
raise ValueError("Hub images cannot be transferred in offline-kit mode")
|
|
82
|
+
if source_mode:
|
|
83
|
+
if not all(
|
|
84
|
+
value is not None
|
|
85
|
+
for value in (
|
|
86
|
+
source_archive_digest,
|
|
87
|
+
source_receiver,
|
|
88
|
+
source_receiver_digest,
|
|
89
|
+
source_snapshot_digest,
|
|
90
|
+
source_runtime_support,
|
|
91
|
+
source_runtime_support_digest,
|
|
92
|
+
)
|
|
93
|
+
):
|
|
94
|
+
raise ValueError("source managed-host preparation inputs are incomplete")
|
|
95
|
+
if archive is not None or archive_digest is not None or control_package is not None:
|
|
96
|
+
raise ValueError("source managed-host preparation cannot use kit inputs")
|
|
97
|
+
elif source_runtime_support is not None or source_runtime_support_digest is not None:
|
|
98
|
+
raise ValueError("kit managed-host preparation cannot use source runtime support")
|
|
99
|
+
elif archive is None or archive_digest is None:
|
|
100
|
+
raise ValueError("kit managed-host preparation inputs are incomplete")
|
|
101
|
+
created = tunnel.ssh(("install", "-d", "-m", "0700", remote_root), timeout=60)
|
|
102
|
+
if created.returncode != 0:
|
|
103
|
+
raise ValueError("standalone remote work directory is unavailable")
|
|
104
|
+
removed = tunnel.ssh(("rm", "-f", "--", remote_archive), timeout=60)
|
|
105
|
+
if removed.returncode != 0:
|
|
106
|
+
raise ValueError("standalone remote archive reset failed")
|
|
107
|
+
remote_source_archive = (
|
|
108
|
+
f"{remote_root}/source-transfer.tar.gz"
|
|
109
|
+
if source_mode and source_archive is not None and source_archive.name.endswith(".tar.gz")
|
|
110
|
+
else f"{remote_root}/source-transfer.tar"
|
|
111
|
+
)
|
|
112
|
+
remote_source_receiver = f"{remote_root}/source-receiver.pyz"
|
|
113
|
+
remote_runtime_support = f"{remote_root}/source-runtime-support.tar"
|
|
114
|
+
if source_mode:
|
|
115
|
+
assert source_archive is not None
|
|
116
|
+
assert source_receiver is not None
|
|
117
|
+
assert source_runtime_support is not None
|
|
118
|
+
tunnel.copy_to(source_archive, remote_source_archive, timeout=min(1800, timeout_seconds))
|
|
119
|
+
tunnel.copy_to(source_receiver, remote_source_receiver, timeout=300)
|
|
120
|
+
tunnel.copy_to(source_runtime_support, remote_runtime_support, timeout=600)
|
|
121
|
+
else:
|
|
122
|
+
assert archive is not None
|
|
123
|
+
tunnel.copy_to(archive, remote_archive, timeout=min(1800, timeout_seconds))
|
|
124
|
+
foundation = stage_foundation_context(
|
|
125
|
+
tunnel,
|
|
126
|
+
handoff_path,
|
|
127
|
+
remote_handoff,
|
|
128
|
+
entra_path,
|
|
129
|
+
remote_entra,
|
|
130
|
+
remote_root,
|
|
131
|
+
)
|
|
132
|
+
if application_state_adoption is not None:
|
|
133
|
+
if not all((remote_adoption_state, remote_adoption_models, remote_adoption_descriptor)):
|
|
134
|
+
raise ValueError("standalone application adoption destinations are incomplete")
|
|
135
|
+
tunnel.copy_to(application_state_adoption.state, remote_adoption_state, timeout=300)
|
|
136
|
+
tunnel.copy_to(
|
|
137
|
+
application_state_adoption.resolved_models, remote_adoption_models, timeout=120
|
|
138
|
+
)
|
|
139
|
+
tunnel.copy_to(
|
|
140
|
+
application_state_adoption.descriptor, remote_adoption_descriptor, timeout=120
|
|
141
|
+
)
|
|
142
|
+
if source_mode:
|
|
143
|
+
assert source_archive_digest is not None
|
|
144
|
+
assert source_receiver_digest is not None
|
|
145
|
+
archive_digest_result = tunnel.ssh(("sha256sum", remote_source_archive), timeout=300)
|
|
146
|
+
receiver_digest_result = tunnel.ssh(("sha256sum", remote_source_receiver), timeout=120)
|
|
147
|
+
requirements_digest_result = tunnel.ssh(("sha256sum", remote_runtime_support), timeout=120)
|
|
148
|
+
if (
|
|
149
|
+
archive_digest_result.returncode != 0
|
|
150
|
+
or archive_digest_result.stdout.split(maxsplit=1)[0] != source_archive_digest
|
|
151
|
+
or receiver_digest_result.returncode != 0
|
|
152
|
+
or receiver_digest_result.stdout.split(maxsplit=1)[0] != source_receiver_digest
|
|
153
|
+
or requirements_digest_result.returncode != 0
|
|
154
|
+
or requirements_digest_result.stdout.split(maxsplit=1)[0]
|
|
155
|
+
!= source_runtime_support_digest
|
|
156
|
+
):
|
|
157
|
+
raise ValueError("source transport digest differs")
|
|
158
|
+
install_cli = _source_cli_installation(remote_root)
|
|
159
|
+
else:
|
|
160
|
+
digest = tunnel.ssh(("sha256sum", remote_archive), timeout=300)
|
|
161
|
+
if digest.returncode != 0 or digest.stdout.split(maxsplit=1)[0] != archive_digest:
|
|
162
|
+
raise ValueError("standalone transport archive digest differs")
|
|
163
|
+
install_cli = _kit_cli_installation(remote_root)
|
|
164
|
+
hub_arguments: tuple[str, ...] = ()
|
|
165
|
+
if hub_selection is not None:
|
|
166
|
+
remote_selection = f"{remote_root}/hub-selection.json"
|
|
167
|
+
tunnel.copy_to(hub_selection.path, remote_selection, timeout=120)
|
|
168
|
+
observed = tunnel.ssh(("sha256sum", remote_selection), timeout=120)
|
|
169
|
+
if (
|
|
170
|
+
observed.returncode != 0
|
|
171
|
+
or not observed.stdout.split()
|
|
172
|
+
or observed.stdout.split()[0] != hub_selection.digest
|
|
173
|
+
):
|
|
174
|
+
raise ValueError("Hub selection transfer digest differs")
|
|
175
|
+
hub_arguments = (
|
|
176
|
+
"--hub-selection",
|
|
177
|
+
remote_selection,
|
|
178
|
+
"--hub-selection-digest",
|
|
179
|
+
hub_selection.digest,
|
|
180
|
+
)
|
|
181
|
+
if control_package is not None:
|
|
182
|
+
install_cli = _stage_control_package(tunnel, remote_root, control_package)
|
|
183
|
+
try:
|
|
184
|
+
catalog_review_arguments = stage_catalog_review_profile(
|
|
185
|
+
catalog_review_profile or CatalogReviewDeploymentProfile.unselected(),
|
|
186
|
+
tunnel=tunnel,
|
|
187
|
+
prepared_root=handoff_path.parent,
|
|
188
|
+
remote_root=remote_root,
|
|
189
|
+
)
|
|
190
|
+
verifier_arguments = stage_operational_evidence_verifier_input(
|
|
191
|
+
operational_evidence_verifier_input,
|
|
192
|
+
tunnel=tunnel,
|
|
193
|
+
prepared_root=handoff_path.parent,
|
|
194
|
+
remote_root=remote_root,
|
|
195
|
+
)
|
|
196
|
+
notification_recipients_arguments = stage_notification_recipients(
|
|
197
|
+
notification_recipients,
|
|
198
|
+
tunnel=tunnel,
|
|
199
|
+
prepared_root=handoff_path.parent,
|
|
200
|
+
remote_root=remote_root,
|
|
201
|
+
)
|
|
202
|
+
development_authority_arguments = stage_development_authority_profile(
|
|
203
|
+
development_authority_profile,
|
|
204
|
+
tunnel=tunnel,
|
|
205
|
+
prepared_root=handoff_path.parent,
|
|
206
|
+
remote_root=remote_root,
|
|
207
|
+
)
|
|
208
|
+
prepare_arguments = (
|
|
209
|
+
f"{remote_root}/venv/bin/python",
|
|
210
|
+
"-m",
|
|
211
|
+
"fdai_deployment_cli.standalone_host",
|
|
212
|
+
"--work-dir",
|
|
213
|
+
app_work,
|
|
214
|
+
*(
|
|
215
|
+
(
|
|
216
|
+
"prepare-source",
|
|
217
|
+
"--source-snapshot",
|
|
218
|
+
f"{remote_root}/source-snapshot",
|
|
219
|
+
"--source-snapshot-digest",
|
|
220
|
+
str(source_snapshot_digest),
|
|
221
|
+
"--runtime-support",
|
|
222
|
+
remote_runtime_support,
|
|
223
|
+
"--runtime-support-digest",
|
|
224
|
+
str(source_runtime_support_digest),
|
|
225
|
+
)
|
|
226
|
+
if source_mode
|
|
227
|
+
else ("prepare", "--kit", f"{remote_root}/kit")
|
|
228
|
+
),
|
|
229
|
+
"--handoff",
|
|
230
|
+
foundation.handoff,
|
|
231
|
+
*(("--entra", foundation.entra) if foundation.entra is not None else ()),
|
|
232
|
+
*foundation.adoption_arguments,
|
|
233
|
+
"--runtime-platform",
|
|
234
|
+
selected_runtime.runtime_platform.value,
|
|
235
|
+
"--database-placement",
|
|
236
|
+
selected_runtime.database_placement.value,
|
|
237
|
+
"--system-node-count",
|
|
238
|
+
str(selected_runtime.system_node_count),
|
|
239
|
+
"--system-node-sku",
|
|
240
|
+
selected_runtime.system_node_sku,
|
|
241
|
+
"--user-node-min-count",
|
|
242
|
+
str(selected_runtime.user_node_min_count),
|
|
243
|
+
"--user-node-max-count",
|
|
244
|
+
str(selected_runtime.user_node_max_count),
|
|
245
|
+
"--user-node-sku",
|
|
246
|
+
selected_runtime.user_node_sku,
|
|
247
|
+
*(
|
|
248
|
+
("--database-sku", selected_runtime.database_sku)
|
|
249
|
+
if selected_runtime.database_sku is not None
|
|
250
|
+
else ()
|
|
251
|
+
),
|
|
252
|
+
*(
|
|
253
|
+
value
|
|
254
|
+
for add_on in selected_runtime.product_profile.add_ons
|
|
255
|
+
for value in ("--product-add-on", add_on.value)
|
|
256
|
+
),
|
|
257
|
+
*(
|
|
258
|
+
value
|
|
259
|
+
for source in selected_runtime.product_profile.observation_permissions.selected_sources
|
|
260
|
+
for value in ("--observation-source", source.value)
|
|
261
|
+
),
|
|
262
|
+
*catalog_review_arguments,
|
|
263
|
+
*hub_arguments,
|
|
264
|
+
*verifier_arguments,
|
|
265
|
+
*notification_recipients_arguments,
|
|
266
|
+
*development_authority_arguments,
|
|
267
|
+
*(("--clear-development-profile",) if clear_development_authority_profile else ()),
|
|
268
|
+
*(
|
|
269
|
+
(
|
|
270
|
+
"--adoption-state",
|
|
271
|
+
remote_adoption_state,
|
|
272
|
+
"--adoption-models",
|
|
273
|
+
remote_adoption_models,
|
|
274
|
+
"--adoption-descriptor",
|
|
275
|
+
remote_adoption_descriptor,
|
|
276
|
+
)
|
|
277
|
+
if application_state_adoption is not None
|
|
278
|
+
else ()
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
commands = (
|
|
282
|
+
*(
|
|
283
|
+
(
|
|
284
|
+
("clean-source", ("rm", "-rf", "--", f"{remote_root}/source-snapshot"), 300),
|
|
285
|
+
(
|
|
286
|
+
"receive-source",
|
|
287
|
+
(
|
|
288
|
+
"python3",
|
|
289
|
+
remote_source_receiver,
|
|
290
|
+
"--archive",
|
|
291
|
+
remote_source_archive,
|
|
292
|
+
"--destination",
|
|
293
|
+
f"{remote_root}/source-snapshot",
|
|
294
|
+
"--archive-digest",
|
|
295
|
+
str(source_archive_digest),
|
|
296
|
+
"--snapshot-digest",
|
|
297
|
+
str(source_snapshot_digest),
|
|
298
|
+
),
|
|
299
|
+
1800,
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
if source_mode
|
|
303
|
+
else (
|
|
304
|
+
("clean-kit", ("rm", "-rf", "--", f"{remote_root}/kit"), 300),
|
|
305
|
+
("extract-kit", ("tar", "-xzf", remote_archive, "-C", remote_root), 1800),
|
|
306
|
+
)
|
|
307
|
+
),
|
|
308
|
+
("clean-venv", ("rm", "-rf", "--", f"{remote_root}/venv"), 300),
|
|
309
|
+
("create-venv", ("python3", "-m", "venv", f"{remote_root}/venv"), 300),
|
|
310
|
+
*install_cli,
|
|
311
|
+
("create-workdir", ("install", "-d", "-m", "0700", app_work), 60),
|
|
312
|
+
("prepare", prepare_arguments, 1800),
|
|
313
|
+
)
|
|
314
|
+
setup = None
|
|
315
|
+
for step, command, limit in commands:
|
|
316
|
+
setup = tunnel.ssh(command, timeout=min(limit, timeout_seconds))
|
|
317
|
+
if setup.returncode != 0:
|
|
318
|
+
raise ValueError(f"standalone managed-host preparation failed: step={step}")
|
|
319
|
+
except (OSError, RuntimeError, ValueError, subprocess.SubprocessError) as setup_failure:
|
|
320
|
+
try:
|
|
321
|
+
_cleanup_catalog_review_staging(tunnel, remote_root=remote_root)
|
|
322
|
+
except ValueError as cleanup_failure:
|
|
323
|
+
raise cleanup_failure from setup_failure
|
|
324
|
+
raise
|
|
325
|
+
_cleanup_catalog_review_staging(tunnel, remote_root=remote_root)
|
|
326
|
+
if setup is None:
|
|
327
|
+
raise ValueError("standalone managed-host preparation returned no result")
|
|
328
|
+
try:
|
|
329
|
+
result = json.loads(setup.stdout)
|
|
330
|
+
except json.JSONDecodeError as exc:
|
|
331
|
+
raise ValueError("standalone managed-host preparation returned invalid output") from exc
|
|
332
|
+
if (
|
|
333
|
+
not isinstance(result, dict)
|
|
334
|
+
or result.get("state") != "prepared"
|
|
335
|
+
or type(result.get("focused_private_access")) is not bool
|
|
336
|
+
):
|
|
337
|
+
raise ValueError("standalone managed-host preparation result is invalid")
|
|
338
|
+
return {str(key): value for key, value in result.items()}
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _kit_cli_installation(remote_root: str) -> tuple[tuple[str, tuple[str, ...], int], ...]:
|
|
342
|
+
return (
|
|
343
|
+
(
|
|
344
|
+
"install-kit-cli",
|
|
345
|
+
(
|
|
346
|
+
f"{remote_root}/venv/bin/pip",
|
|
347
|
+
"install",
|
|
348
|
+
"--no-index",
|
|
349
|
+
"--no-cache-dir",
|
|
350
|
+
"--find-links",
|
|
351
|
+
f"{remote_root}/kit/python",
|
|
352
|
+
"fdai-deployment-cli",
|
|
353
|
+
),
|
|
354
|
+
900,
|
|
355
|
+
),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _source_cli_installation(remote_root: str) -> tuple[tuple[str, tuple[str, ...], int], ...]:
|
|
360
|
+
return (
|
|
361
|
+
(
|
|
362
|
+
"install-source-cli",
|
|
363
|
+
(
|
|
364
|
+
f"{remote_root}/venv/bin/pip",
|
|
365
|
+
"install",
|
|
366
|
+
"--no-cache-dir",
|
|
367
|
+
# The CLI pins the workspace contracts package, which no index publishes.
|
|
368
|
+
f"{remote_root}/source-snapshot/tree/packages/service-contracts",
|
|
369
|
+
f"{remote_root}/source-snapshot/tree/packages/deployment-cli",
|
|
370
|
+
),
|
|
371
|
+
900,
|
|
372
|
+
),
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _stage_control_package(
|
|
377
|
+
tunnel: Any, remote_root: str, control_package: ControlPackage
|
|
378
|
+
) -> tuple[tuple[str, tuple[str, ...], int], ...]:
|
|
379
|
+
"""Copy the locally verified wheelhouse and require the same bytes on the host."""
|
|
380
|
+
|
|
381
|
+
remote_control = f"{remote_root}/control.tar.gz"
|
|
382
|
+
control_root = f"{remote_root}/control"
|
|
383
|
+
if tunnel.ssh(("rm", "-f", "--", remote_control), timeout=60).returncode != 0:
|
|
384
|
+
raise ValueError("standalone control package reset failed")
|
|
385
|
+
tunnel.copy_to(control_package.archive, remote_control, timeout=300)
|
|
386
|
+
digest = tunnel.ssh(("sha256sum", remote_control), timeout=120)
|
|
387
|
+
if (
|
|
388
|
+
digest.returncode != 0
|
|
389
|
+
or digest.stdout.split(maxsplit=1)[0] != control_package.archive_digest
|
|
390
|
+
):
|
|
391
|
+
raise ValueError("standalone control package digest differs")
|
|
392
|
+
package = f"{control_root}/{PACKAGE_ROOT}"
|
|
393
|
+
return (
|
|
394
|
+
("clean-control-package", ("rm", "-rf", "--", control_root), 120),
|
|
395
|
+
("create-control-package-dir", ("install", "-d", "-m", "0700", control_root), 60),
|
|
396
|
+
("extract-control-package", ("tar", "-xzf", remote_control, "-C", control_root), 300),
|
|
397
|
+
(
|
|
398
|
+
"install-control-package",
|
|
399
|
+
(
|
|
400
|
+
f"{remote_root}/venv/bin/pip",
|
|
401
|
+
"install",
|
|
402
|
+
"--no-index",
|
|
403
|
+
"--no-cache-dir",
|
|
404
|
+
"--find-links",
|
|
405
|
+
f"{package}/wheels",
|
|
406
|
+
"--requirement",
|
|
407
|
+
f"{package}/requirements.txt",
|
|
408
|
+
),
|
|
409
|
+
900,
|
|
410
|
+
),
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _cleanup_catalog_review_staging(tunnel: Any, *, remote_root: str) -> None:
|
|
415
|
+
"""Require removal and absence readback for both staged credential files."""
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
cleanup = tunnel.ssh(
|
|
419
|
+
(
|
|
420
|
+
"rm",
|
|
421
|
+
"-f",
|
|
422
|
+
"--",
|
|
423
|
+
f"{remote_root}/catalog-review-profile.json",
|
|
424
|
+
f"{remote_root}/catalog-review-private-key.pem",
|
|
425
|
+
),
|
|
426
|
+
timeout=60,
|
|
427
|
+
)
|
|
428
|
+
profile_absent = tunnel.ssh(
|
|
429
|
+
("test", "!", "-e", f"{remote_root}/catalog-review-profile.json"),
|
|
430
|
+
timeout=60,
|
|
431
|
+
)
|
|
432
|
+
key_absent = tunnel.ssh(
|
|
433
|
+
("test", "!", "-e", f"{remote_root}/catalog-review-private-key.pem"),
|
|
434
|
+
timeout=60,
|
|
435
|
+
)
|
|
436
|
+
except (OSError, RuntimeError, ValueError, subprocess.SubprocessError) as exc:
|
|
437
|
+
raise ValueError("standalone catalog review profile cleanup is incomplete") from exc
|
|
438
|
+
if any(result.returncode != 0 for result in (cleanup, profile_absent, key_absent)):
|
|
439
|
+
raise ValueError("standalone catalog review profile cleanup is incomplete")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
__all__ = ["prepare_remote"]
|