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,191 @@
|
|
|
1
|
+
"""Revalidate retained kit inputs; cache metadata never grants trust or deployment authority."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import fcntl
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import stat
|
|
10
|
+
from collections.abc import Iterator
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from fdai_deployment_cli.contracts import load_json_object
|
|
15
|
+
from fdai_deployment_cli.execution_copies import new_execution_copy
|
|
16
|
+
from fdai_deployment_cli.offline_kit import (
|
|
17
|
+
MANIFEST_NAME,
|
|
18
|
+
SIGNATURE_NAME,
|
|
19
|
+
MaterializedOfflineArtifacts,
|
|
20
|
+
OfflineKitVerification,
|
|
21
|
+
_scan_tree,
|
|
22
|
+
)
|
|
23
|
+
from fdai_deployment_cli.private_output import read_private_bytes, write_private_output
|
|
24
|
+
|
|
25
|
+
_MAX_CACHE_ENTRIES = 40_000
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def path_present(path: Path) -> bool:
|
|
29
|
+
"""Detect even dangling links so exclusive destinations are never silently reused."""
|
|
30
|
+
|
|
31
|
+
return os.path.lexists(path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@contextmanager
|
|
35
|
+
def acquisition_lock(work_dir: Path) -> Iterator[None]:
|
|
36
|
+
"""Serialize local acquisition only, never replacing target or execution locks."""
|
|
37
|
+
|
|
38
|
+
descriptor = os.open(work_dir, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
39
|
+
try:
|
|
40
|
+
details = os.fstat(descriptor)
|
|
41
|
+
if stat.S_IMODE(details.st_mode) != 0o700 or details.st_uid != os.geteuid():
|
|
42
|
+
raise ValueError("deployment kit work directory must be current-UID mode 0700")
|
|
43
|
+
try:
|
|
44
|
+
fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
45
|
+
except BlockingIOError as exc:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"deployment kit acquisition is already active in this work directory"
|
|
48
|
+
) from exc
|
|
49
|
+
yield
|
|
50
|
+
finally:
|
|
51
|
+
os.close(descriptor)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def bind_online_source(work_dir: Path, *, url: str, default_url: str) -> bool:
|
|
55
|
+
"""Pin the requested URL digest, not its remote provenance or authenticity.
|
|
56
|
+
|
|
57
|
+
Unbound retained inputs are candidates only for the default versioned source.
|
|
58
|
+
The returned legacy flag requires version checking after full signature verification.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
marker = work_dir / "online-source.json"
|
|
62
|
+
source_digest = hashlib.sha256(url.encode("utf-8")).hexdigest()
|
|
63
|
+
if path_present(marker):
|
|
64
|
+
value = load_json_object(
|
|
65
|
+
read_private_bytes(marker, max_bytes=4096),
|
|
66
|
+
label="online kit source record",
|
|
67
|
+
max_bytes=4096,
|
|
68
|
+
)
|
|
69
|
+
kind = value.get("kind")
|
|
70
|
+
if (
|
|
71
|
+
set(value) != {"schema_version", "source_sha256", "kind"}
|
|
72
|
+
or value.get("schema_version") != "fdai.online-kit-source.v1"
|
|
73
|
+
or not isinstance(kind, str)
|
|
74
|
+
or kind not in ("request", "legacy-retained")
|
|
75
|
+
or value.get("source_sha256") != source_digest
|
|
76
|
+
):
|
|
77
|
+
raise ValueError(
|
|
78
|
+
"retained deployment kit source differs or is invalid; preserve the work directory for review"
|
|
79
|
+
)
|
|
80
|
+
return kind == "legacy-retained"
|
|
81
|
+
legacy = any(
|
|
82
|
+
path_present(work_dir / name)
|
|
83
|
+
for name in ("downloaded-kit.tar.gz", "kit", "verified", "bundle")
|
|
84
|
+
)
|
|
85
|
+
if legacy and url != default_url:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"retained deployment kit source is unbound; an online URL override cannot reuse it"
|
|
88
|
+
)
|
|
89
|
+
write_private_output(
|
|
90
|
+
marker,
|
|
91
|
+
json.dumps(
|
|
92
|
+
{
|
|
93
|
+
"schema_version": "fdai.online-kit-source.v1",
|
|
94
|
+
"source_sha256": source_digest,
|
|
95
|
+
"kind": "legacy-retained" if legacy else "request",
|
|
96
|
+
},
|
|
97
|
+
sort_keys=True,
|
|
98
|
+
separators=(",", ":"),
|
|
99
|
+
)
|
|
100
|
+
+ "\n",
|
|
101
|
+
)
|
|
102
|
+
return legacy
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def validate_retained_archive(path: Path, *, max_bytes: int) -> None:
|
|
106
|
+
"""Reject unsafe cached archive metadata before reading; signatures still decide trust."""
|
|
107
|
+
|
|
108
|
+
details = path.lstat()
|
|
109
|
+
if (
|
|
110
|
+
not stat.S_ISREG(details.st_mode)
|
|
111
|
+
or stat.S_IMODE(details.st_mode) != 0o600
|
|
112
|
+
or details.st_uid != os.geteuid()
|
|
113
|
+
or details.st_nlink != 1
|
|
114
|
+
or not 0 < details.st_size <= max_bytes
|
|
115
|
+
):
|
|
116
|
+
raise ValueError(
|
|
117
|
+
"retained deployment kit archive is unsafe or empty; preserve it for review"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def validate_cached_tree(
|
|
122
|
+
root: Path, *, executable: Path | None = None, tool_directory: Path | None = None
|
|
123
|
+
) -> None:
|
|
124
|
+
"""Require private, owned, non-linked cached files; content is checked separately."""
|
|
125
|
+
|
|
126
|
+
directories = [root]
|
|
127
|
+
entries = 0
|
|
128
|
+
while directories:
|
|
129
|
+
directory = directories.pop()
|
|
130
|
+
details = directory.lstat()
|
|
131
|
+
mode = stat.S_IMODE(details.st_mode)
|
|
132
|
+
# mkdir(parents=True, mode=0700) uses the umask for intermediate parents.
|
|
133
|
+
# The enclosing 0700 root still prevents external traversal; no descendant
|
|
134
|
+
# may be writable by another principal or owned by one.
|
|
135
|
+
if (
|
|
136
|
+
not stat.S_ISDIR(details.st_mode)
|
|
137
|
+
or (directory == root and mode != 0o700)
|
|
138
|
+
or mode & 0o022
|
|
139
|
+
or details.st_uid != os.geteuid()
|
|
140
|
+
):
|
|
141
|
+
raise ValueError("retained deployment kit directory is unsafe; preserve it for review")
|
|
142
|
+
for path in directory.iterdir():
|
|
143
|
+
entries += 1
|
|
144
|
+
if entries > _MAX_CACHE_ENTRIES:
|
|
145
|
+
raise ValueError("retained deployment kit exceeds its entry limit")
|
|
146
|
+
details = path.lstat()
|
|
147
|
+
if stat.S_ISDIR(details.st_mode):
|
|
148
|
+
directories.append(path)
|
|
149
|
+
continue
|
|
150
|
+
is_tool = tool_directory is not None and path.parent == tool_directory
|
|
151
|
+
expected_mode = 0o700 if path == executable or is_tool else 0o600
|
|
152
|
+
if (
|
|
153
|
+
not stat.S_ISREG(details.st_mode)
|
|
154
|
+
or stat.S_IMODE(details.st_mode) != expected_mode
|
|
155
|
+
or details.st_uid != os.geteuid()
|
|
156
|
+
or details.st_nlink != 1
|
|
157
|
+
):
|
|
158
|
+
raise ValueError("retained deployment kit file is unsafe; preserve it for review")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def verify_retained_artifacts(
|
|
162
|
+
destination: Path, verification: OfflineKitVerification
|
|
163
|
+
) -> MaterializedOfflineArtifacts:
|
|
164
|
+
"""Recheck the entire existing snapshot against newly authenticated source digests."""
|
|
165
|
+
|
|
166
|
+
terraform = destination / verification.terraform_binary
|
|
167
|
+
validate_cached_tree(destination, executable=terraform, tool_directory=destination / "bin")
|
|
168
|
+
files, sizes, _total = _scan_tree(destination)
|
|
169
|
+
if (
|
|
170
|
+
files != dict(verification.file_digests)
|
|
171
|
+
or sizes != dict(verification.file_sizes)
|
|
172
|
+
or any(path_present(destination / name) for name in (MANIFEST_NAME, SIGNATURE_NAME))
|
|
173
|
+
):
|
|
174
|
+
raise ValueError(
|
|
175
|
+
"retained deployment kit snapshot differs or is incomplete; preserve it for review"
|
|
176
|
+
)
|
|
177
|
+
return MaterializedOfflineArtifacts(
|
|
178
|
+
terraform_binary=terraform,
|
|
179
|
+
provider_mirror=destination / verification.provider_mirror_prefix,
|
|
180
|
+
deployment_bundle=destination / verification.deployment_bundle,
|
|
181
|
+
python_wheels=destination / "python",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def execution_bundle_destination(work_dir: Path) -> Path:
|
|
186
|
+
"""Create a fresh bundle destination without touching previous execution evidence."""
|
|
187
|
+
|
|
188
|
+
original = work_dir / "bundle"
|
|
189
|
+
if not path_present(original):
|
|
190
|
+
return original
|
|
191
|
+
return new_execution_copy(work_dir, "bundle-recheck-") / "bundle"
|