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.
Files changed (203) hide show
  1. fdai_deployment_cli/__about__.py +3 -0
  2. fdai_deployment_cli/__init__.py +5 -0
  3. fdai_deployment_cli/__main__.py +5 -0
  4. fdai_deployment_cli/acr_publish.py +585 -0
  5. fdai_deployment_cli/aks_catalog_review.py +137 -0
  6. fdai_deployment_cli/aks_historical_reconciliation.py +732 -0
  7. fdai_deployment_cli/aks_job_environment.py +59 -0
  8. fdai_deployment_cli/aks_job_execution.py +794 -0
  9. fdai_deployment_cli/aks_kubernetes_defaults.py +66 -0
  10. fdai_deployment_cli/aks_preflight.py +422 -0
  11. fdai_deployment_cli/aks_readiness.py +220 -0
  12. fdai_deployment_cli/aks_service_update.py +180 -0
  13. fdai_deployment_cli/aks_workload_jobs.py +281 -0
  14. fdai_deployment_cli/application_state_adoption.py +347 -0
  15. fdai_deployment_cli/azure_actor.py +61 -0
  16. fdai_deployment_cli/azure_naming.py +252 -0
  17. fdai_deployment_cli/azure_retail_prices.py +173 -0
  18. fdai_deployment_cli/bootstrap_approval.py +99 -0
  19. fdai_deployment_cli/bootstrap_channel.py +321 -0
  20. fdai_deployment_cli/bootstrap_executor.py +311 -0
  21. fdai_deployment_cli/bootstrap_plan.py +237 -0
  22. fdai_deployment_cli/bootstrap_reconcile.py +402 -0
  23. fdai_deployment_cli/bootstrap_support.py +138 -0
  24. fdai_deployment_cli/bootstrap_worker.py +448 -0
  25. fdai_deployment_cli/bundle.py +352 -0
  26. fdai_deployment_cli/capacity.py +143 -0
  27. fdai_deployment_cli/catalog_review_profile.py +306 -0
  28. fdai_deployment_cli/cli.py +798 -0
  29. fdai_deployment_cli/cli_help.py +214 -0
  30. fdai_deployment_cli/cli_parser.py +791 -0
  31. fdai_deployment_cli/cli_plan.py +450 -0
  32. fdai_deployment_cli/compiler.py +99 -0
  33. fdai_deployment_cli/console_artifact.py +218 -0
  34. fdai_deployment_cli/console_config.py +214 -0
  35. fdai_deployment_cli/console_update.py +832 -0
  36. fdai_deployment_cli/contracts.py +307 -0
  37. fdai_deployment_cli/control_package.py +140 -0
  38. fdai_deployment_cli/deadline_transport.py +70 -0
  39. fdai_deployment_cli/deployment_cost.py +131 -0
  40. fdai_deployment_cli/deployment_deadline.py +28 -0
  41. fdai_deployment_cli/deployment_kit.py +790 -0
  42. fdai_deployment_cli/deployment_kit_cache.py +191 -0
  43. fdai_deployment_cli/deployment_progress.py +648 -0
  44. fdai_deployment_cli/development_authority_cli.py +56 -0
  45. fdai_deployment_cli/development_authority_profile.py +319 -0
  46. fdai_deployment_cli/doctor.py +136 -0
  47. fdai_deployment_cli/entitlement_preflight.py +68 -0
  48. fdai_deployment_cli/entitlement_transport.py +49 -0
  49. fdai_deployment_cli/entra_bootstrap.py +579 -0
  50. fdai_deployment_cli/entra_control_profile.py +214 -0
  51. fdai_deployment_cli/entra_graph.py +349 -0
  52. fdai_deployment_cli/entra_profiles.py +215 -0
  53. fdai_deployment_cli/entra_source.py +244 -0
  54. fdai_deployment_cli/execution_bundle.py +478 -0
  55. fdai_deployment_cli/execution_copies.py +63 -0
  56. fdai_deployment_cli/foundation_adoption.py +438 -0
  57. fdai_deployment_cli/foundation_adoption_evidence.py +245 -0
  58. fdai_deployment_cli/foundation_adoption_host.py +157 -0
  59. fdai_deployment_cli/foundation_adoption_transport.py +51 -0
  60. fdai_deployment_cli/foundation_failure.py +72 -0
  61. fdai_deployment_cli/foundation_image.py +168 -0
  62. fdai_deployment_cli/foundation_input.py +235 -0
  63. fdai_deployment_cli/foundation_output.py +154 -0
  64. fdai_deployment_cli/foundation_plan.py +381 -0
  65. fdai_deployment_cli/foundation_process.py +64 -0
  66. fdai_deployment_cli/foundation_progress.py +231 -0
  67. fdai_deployment_cli/github_actions.py +171 -0
  68. fdai_deployment_cli/github_apply_artifact.py +259 -0
  69. fdai_deployment_cli/github_artifact_io.py +48 -0
  70. fdai_deployment_cli/github_dispatch.py +248 -0
  71. fdai_deployment_cli/github_plan_artifact.py +181 -0
  72. fdai_deployment_cli/github_provider_artifact.py +170 -0
  73. fdai_deployment_cli/github_transport.py +46 -0
  74. fdai_deployment_cli/github_workflow_values.py +312 -0
  75. fdai_deployment_cli/hub_image_deploy.py +354 -0
  76. fdai_deployment_cli/hub_image_stage.py +162 -0
  77. fdai_deployment_cli/hub_image_trust.py +281 -0
  78. fdai_deployment_cli/hub_ownership.py +161 -0
  79. fdai_deployment_cli/hub_workload_handoff.py +264 -0
  80. fdai_deployment_cli/hub_workload_handoff_inputs.py +227 -0
  81. fdai_deployment_cli/identity_profile.py +227 -0
  82. fdai_deployment_cli/image_targets.py +197 -0
  83. fdai_deployment_cli/installation_scope.py +280 -0
  84. fdai_deployment_cli/license.py +311 -0
  85. fdai_deployment_cli/license_issue.py +239 -0
  86. fdai_deployment_cli/lifecycle_authorization.py +252 -0
  87. fdai_deployment_cli/lifecycle_commands.py +173 -0
  88. fdai_deployment_cli/lifecycle_configuration.py +722 -0
  89. fdai_deployment_cli/lifecycle_configuration_artifact.py +45 -0
  90. fdai_deployment_cli/lifecycle_configuration_cli.py +324 -0
  91. fdai_deployment_cli/lifecycle_configuration_document.py +74 -0
  92. fdai_deployment_cli/lifecycle_configuration_entities.py +146 -0
  93. fdai_deployment_cli/lifecycle_configuration_package.py +492 -0
  94. fdai_deployment_cli/lifecycle_configuration_publication.py +277 -0
  95. fdai_deployment_cli/lifecycle_configuration_publication_cli.py +191 -0
  96. fdai_deployment_cli/lifecycle_configuration_sealing.py +127 -0
  97. fdai_deployment_cli/lifecycle_enrollment.py +45 -0
  98. fdai_deployment_cli/lifecycle_infrastructure_agent.py +174 -0
  99. fdai_deployment_cli/lifecycle_installation_binding.py +65 -0
  100. fdai_deployment_cli/lifecycle_native.py +247 -0
  101. fdai_deployment_cli/lifecycle_native_reconciliation.py +568 -0
  102. fdai_deployment_cli/lifecycle_native_transition.py +339 -0
  103. fdai_deployment_cli/lifecycle_ownership_exclusion.py +78 -0
  104. fdai_deployment_cli/lifecycle_plan.py +800 -0
  105. fdai_deployment_cli/lifecycle_promotion.py +185 -0
  106. fdai_deployment_cli/lifecycle_release.py +308 -0
  107. fdai_deployment_cli/lifecycle_self_upgrade.py +202 -0
  108. fdai_deployment_cli/lifecycle_upgrade_bundle.py +176 -0
  109. fdai_deployment_cli/notification_recipients_input.py +222 -0
  110. fdai_deployment_cli/observer_artifact.py +106 -0
  111. fdai_deployment_cli/oci_archive.py +353 -0
  112. fdai_deployment_cli/offline_kit.py +797 -0
  113. fdai_deployment_cli/offline_prepare.py +135 -0
  114. fdai_deployment_cli/operational_evidence_verifier_input.py +575 -0
  115. fdai_deployment_cli/plan_input.py +101 -0
  116. fdai_deployment_cli/private_output.py +192 -0
  117. fdai_deployment_cli/profile.py +70 -0
  118. fdai_deployment_cli/progress.py +183 -0
  119. fdai_deployment_cli/readiness.py +286 -0
  120. fdai_deployment_cli/run_command_receiver.py +418 -0
  121. fdai_deployment_cli/runtime_build.py +386 -0
  122. fdai_deployment_cli/runtime_profile.py +287 -0
  123. fdai_deployment_cli/runtime_release.py +745 -0
  124. fdai_deployment_cli/runtime_stage.py +82 -0
  125. fdai_deployment_cli/runtime_support_installation.py +215 -0
  126. fdai_deployment_cli/simulation.py +80 -0
  127. fdai_deployment_cli/source_application_inputs.py +397 -0
  128. fdai_deployment_cli/source_azure.py +586 -0
  129. fdai_deployment_cli/source_change_cli.py +107 -0
  130. fdai_deployment_cli/source_change_parser.py +75 -0
  131. fdai_deployment_cli/source_change_plan.py +181 -0
  132. fdai_deployment_cli/source_changed_update.py +196 -0
  133. fdai_deployment_cli/source_deploy.py +170 -0
  134. fdai_deployment_cli/source_foundation.py +174 -0
  135. fdai_deployment_cli/source_host_tools.py +163 -0
  136. fdai_deployment_cli/source_image_stage.py +400 -0
  137. fdai_deployment_cli/source_input.py +173 -0
  138. fdai_deployment_cli/source_projection.py +164 -0
  139. fdai_deployment_cli/source_receiver.py +76 -0
  140. fdai_deployment_cli/source_recovery.py +264 -0
  141. fdai_deployment_cli/source_runtime.py +81 -0
  142. fdai_deployment_cli/source_runtime_support.py +368 -0
  143. fdai_deployment_cli/source_service_update.py +726 -0
  144. fdai_deployment_cli/source_service_update_cli.py +47 -0
  145. fdai_deployment_cli/source_snapshot.py +178 -0
  146. fdai_deployment_cli/source_teardown.py +574 -0
  147. fdai_deployment_cli/source_terraform.py +85 -0
  148. fdai_deployment_cli/source_transport.py +399 -0
  149. fdai_deployment_cli/standalone_aks_inventory.py +558 -0
  150. fdai_deployment_cli/standalone_aks_job_execution.py +769 -0
  151. fdai_deployment_cli/standalone_aks_nodepool_guard.py +141 -0
  152. fdai_deployment_cli/standalone_application.py +977 -0
  153. fdai_deployment_cli/standalone_application_completion.py +151 -0
  154. fdai_deployment_cli/standalone_application_input_recovery.py +107 -0
  155. fdai_deployment_cli/standalone_application_recovery.py +116 -0
  156. fdai_deployment_cli/standalone_approval_input.py +24 -0
  157. fdai_deployment_cli/standalone_catalog_checkpoint.py +122 -0
  158. fdai_deployment_cli/standalone_catalog_review.py +251 -0
  159. fdai_deployment_cli/standalone_checkpoint_failure.py +127 -0
  160. fdai_deployment_cli/standalone_console_publish.py +174 -0
  161. fdai_deployment_cli/standalone_database_principals.py +41 -0
  162. fdai_deployment_cli/standalone_deploy.py +638 -0
  163. fdai_deployment_cli/standalone_development_authority.py +166 -0
  164. fdai_deployment_cli/standalone_foundation_adoption.py +87 -0
  165. fdai_deployment_cli/standalone_foundation_transition.py +767 -0
  166. fdai_deployment_cli/standalone_host.py +4608 -0
  167. fdai_deployment_cli/standalone_host_rebind.py +226 -0
  168. fdai_deployment_cli/standalone_host_residual.py +104 -0
  169. fdai_deployment_cli/standalone_host_state.py +145 -0
  170. fdai_deployment_cli/standalone_host_values.py +563 -0
  171. fdai_deployment_cli/standalone_kubernetes_tools.py +100 -0
  172. fdai_deployment_cli/standalone_license_installation.py +177 -0
  173. fdai_deployment_cli/standalone_management_egress.py +83 -0
  174. fdai_deployment_cli/standalone_migration_evidence.py +38 -0
  175. fdai_deployment_cli/standalone_operational_evidence.py +356 -0
  176. fdai_deployment_cli/standalone_planned_outputs.py +147 -0
  177. fdai_deployment_cli/standalone_private_access_probe.py +100 -0
  178. fdai_deployment_cli/standalone_product_profile.py +110 -0
  179. fdai_deployment_cli/standalone_remote_prepare.py +442 -0
  180. fdai_deployment_cli/standalone_residual_apply.py +540 -0
  181. fdai_deployment_cli/standalone_residual_successor.py +431 -0
  182. fdai_deployment_cli/standalone_review.py +266 -0
  183. fdai_deployment_cli/standalone_service_update_finalization.py +116 -0
  184. fdai_deployment_cli/standalone_service_update_recovery.py +277 -0
  185. fdai_deployment_cli/standalone_stage_targets.py +325 -0
  186. fdai_deployment_cli/standalone_status.py +67 -0
  187. fdai_deployment_cli/standalone_terraform_environment.py +237 -0
  188. fdai_deployment_cli/standalone_transfer_cleanup.py +237 -0
  189. fdai_deployment_cli/standalone_trial_activation.py +193 -0
  190. fdai_deployment_cli/state.py +434 -0
  191. fdai_deployment_cli/state_handoff.py +253 -0
  192. fdai_deployment_cli/status_projection.py +97 -0
  193. fdai_deployment_cli/support_install.py +257 -0
  194. fdai_deployment_cli/target.py +46 -0
  195. fdai_deployment_cli/trust/capabilities.json +62 -0
  196. fdai_deployment_cli/trust/deployment-bundle-root.pub +3 -0
  197. fdai_deployment_cli/trust/deployment-release-root.pub +3 -0
  198. fdai_deployment_cli/trust/upstream-signing-key.pub +3 -0
  199. fdai_deployment_cli/trust_roots.py +50 -0
  200. fdai_deployment_cli-0.1.2.dist-info/METADATA +112 -0
  201. fdai_deployment_cli-0.1.2.dist-info/RECORD +203 -0
  202. fdai_deployment_cli-0.1.2.dist-info/WHEEL +4 -0
  203. fdai_deployment_cli-0.1.2.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,47 @@
1
+ """Public CLI input selection for one source service update."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import subprocess
7
+ from pathlib import Path
8
+
9
+ from fdai_deployment_cli.development_authority_cli import (
10
+ selected_development_authority,
11
+ )
12
+ from fdai_deployment_cli.development_authority_profile import (
13
+ DevelopmentAuthorityDeploymentProfile,
14
+ )
15
+
16
+
17
+ def source_service_update_inputs(
18
+ args: argparse.Namespace,
19
+ ) -> tuple[
20
+ Path,
21
+ Path,
22
+ Path,
23
+ DevelopmentAuthorityDeploymentProfile | None,
24
+ bool,
25
+ ]:
26
+ """Resolve paths and the Core-only development-authority operation."""
27
+
28
+ source = args.source.absolute()
29
+ application = args.application_work_dir.absolute()
30
+ selected = args.work_dir
31
+ if selected is None:
32
+ commit = subprocess.run(
33
+ ("git", "rev-parse", "HEAD"),
34
+ cwd=source,
35
+ check=True,
36
+ capture_output=True,
37
+ text=True,
38
+ timeout=30,
39
+ ).stdout.strip()
40
+ selected = Path.home() / ".local/state/fdai/source-service-updates" / commit / args.service
41
+ profile, clear = selected_development_authority(args, cwd=Path.cwd())
42
+ if (profile is not None or clear) and args.service != "core-control-plane":
43
+ raise ValueError("development authority changes require a Core service update")
44
+ return source, application, selected, profile, clear
45
+
46
+
47
+ __all__ = ["source_service_update_inputs"]
@@ -0,0 +1,178 @@
1
+ """Materialize only pinned Git files for connected builds and private-host transfer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import os
7
+ import stat
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from fdai_deployment_cli.contracts import canonical_bytes, canonical_digest, load_json_object
12
+ from fdai_deployment_cli.private_output import read_private_bytes, write_private_bytes
13
+ from fdai_deployment_cli.source_input import SourceDeploymentInput, _git, _read_tracked
14
+
15
+ _MANIFEST = "source-input.json"
16
+ _FULL_SCHEMA = "fdai.source-snapshot.v1"
17
+ _PROJECTED_SCHEMA = "fdai.source-snapshot.v2"
18
+
19
+
20
+ def materialize_source(
21
+ source: SourceDeploymentInput,
22
+ destination: Path,
23
+ *,
24
+ selected_paths: tuple[str, ...] | None = None,
25
+ purpose: str | None = None,
26
+ ) -> str:
27
+ """Create a fresh private execution copy, excluding every ignored or untracked input.
28
+
29
+ Return the manifest digest for independent retention. Failure preserves the partial
30
+ destination for inspection; it never repairs, reuses or deletes prior deployment state.
31
+ """
32
+ source.reverify()
33
+ destination.mkdir(mode=0o700)
34
+ tree = destination / "tree"
35
+ tree.mkdir(mode=0o700)
36
+ selected = set(selected_paths) if selected_paths is not None else None
37
+ if selected is not None and (
38
+ not selected
39
+ or purpose is None
40
+ or not purpose.strip()
41
+ or len(purpose) > 128
42
+ or any(character not in "abcdefghijklmnopqrstuvwxyz0123456789-._:" for character in purpose)
43
+ ):
44
+ raise ValueError("projected source snapshot requires a bounded purpose and file set")
45
+ records: list[dict[str, str]] = []
46
+ for entry in _git(source.root, "ls-tree", "-rz", "--full-tree", source.commit).split(b"\0"):
47
+ if not entry:
48
+ continue
49
+ metadata, raw_path = entry.split(b"\t", 1)
50
+ mode, kind, _blob = metadata.decode("ascii").split()
51
+ relative = raw_path.decode("utf-8")
52
+ if selected is not None and relative not in selected:
53
+ continue
54
+ if kind != "blob" or mode not in {"100644", "100755", "120000"}:
55
+ raise ValueError("source snapshot contains an unsupported input")
56
+ content = _read_tracked(source.root, relative, mode=mode)
57
+ output = tree / relative
58
+ for parent in reversed(output.parents):
59
+ if parent == tree or not parent.is_relative_to(tree):
60
+ continue
61
+ parent.mkdir(mode=0o700, exist_ok=True)
62
+ if mode == "120000":
63
+ output.symlink_to(os.fsdecode(content))
64
+ else:
65
+ write_private_bytes(output, content)
66
+ if mode == "100755":
67
+ output.chmod(0o700)
68
+ records.append(
69
+ {"path": relative, "mode": mode, "sha256": hashlib.sha256(content).hexdigest()}
70
+ )
71
+ source.reverify()
72
+ content_digest = canonical_digest({"files": records})
73
+ if selected is None and content_digest != source.content_digest:
74
+ raise ValueError("source snapshot differs from the selected input")
75
+ if selected is not None and {record["path"] for record in records} != selected:
76
+ raise ValueError("projected source snapshot contains unavailable inputs")
77
+ manifest: dict[str, object] = {
78
+ "schema_version": _FULL_SCHEMA if selected is None else _PROJECTED_SCHEMA,
79
+ "source": source.to_mapping(),
80
+ "files": records,
81
+ }
82
+ if selected is not None:
83
+ manifest["projection"] = {
84
+ "purpose": purpose,
85
+ "file_count": len(records),
86
+ "content_digest": content_digest,
87
+ }
88
+ digest = canonical_digest(manifest)
89
+ write_private_bytes(destination / _MANIFEST, canonical_bytes(manifest))
90
+ verify_source_snapshot(destination, expected_digest=digest)
91
+ return digest
92
+
93
+
94
+ def verify_source_snapshot(directory: Path, *, expected_digest: str) -> dict[str, Any]:
95
+ """Recheck an execution snapshot against an independently retained manifest digest."""
96
+ raw = read_private_bytes(directory / _MANIFEST, max_bytes=16 * 1024 * 1024)
97
+ manifest = load_json_object(raw, label="source snapshot", max_bytes=16 * 1024 * 1024)
98
+ if canonical_digest(manifest) != expected_digest or raw != canonical_bytes(manifest):
99
+ raise ValueError("source snapshot manifest differs from the retained digest")
100
+ schema = manifest.get("schema_version")
101
+ expected_keys = (
102
+ {"schema_version", "source", "files"}
103
+ if schema == _FULL_SCHEMA
104
+ else {"schema_version", "source", "projection", "files"}
105
+ if schema == _PROJECTED_SCHEMA
106
+ else set()
107
+ )
108
+ if set(manifest) != expected_keys:
109
+ raise ValueError("source snapshot schema is invalid")
110
+ source = manifest["source"]
111
+ records = manifest["files"]
112
+ if (
113
+ not isinstance(source, dict)
114
+ or source.get("provenance") != "operator-selected-source"
115
+ or source.get("release_signature_verified") is not False
116
+ ):
117
+ raise ValueError("source snapshot cannot claim release trust")
118
+ projection = manifest.get("projection")
119
+ expected_count = (
120
+ source.get("file_count")
121
+ if schema == _FULL_SCHEMA
122
+ else projection.get("file_count")
123
+ if isinstance(projection, dict)
124
+ else None
125
+ )
126
+ expected_content_digest = (
127
+ source.get("content_digest")
128
+ if schema == _FULL_SCHEMA
129
+ else projection.get("content_digest")
130
+ if isinstance(projection, dict)
131
+ else None
132
+ )
133
+ if (
134
+ not isinstance(records, list)
135
+ or len(records) != expected_count
136
+ or (
137
+ schema == _PROJECTED_SCHEMA
138
+ and (
139
+ not isinstance(projection, dict)
140
+ or set(projection) != {"purpose", "file_count", "content_digest"}
141
+ or not isinstance(projection.get("purpose"), str)
142
+ )
143
+ )
144
+ ):
145
+ raise ValueError("source snapshot inventory is invalid")
146
+ if canonical_digest({"files": records}) != expected_content_digest:
147
+ raise ValueError("source snapshot content binding is invalid")
148
+ tree = directory / "tree"
149
+ if tree.is_symlink() or not tree.is_dir():
150
+ raise ValueError("source snapshot root is invalid")
151
+ paths = set()
152
+ for record in records:
153
+ relative, mode = record["path"], record["mode"]
154
+ if relative in paths:
155
+ raise ValueError("source snapshot repeats a file")
156
+ paths.add(relative)
157
+ content = _read_tracked(tree, relative, mode=mode)
158
+ if hashlib.sha256(content).hexdigest() != record["sha256"]:
159
+ raise ValueError("source snapshot content changed")
160
+ if mode == "120000":
161
+ target = (tree / relative).resolve(strict=True)
162
+ if not target.is_relative_to(tree.resolve()) or target.relative_to(
163
+ tree
164
+ ).as_posix() not in {item["path"] for item in records}:
165
+ raise ValueError("source snapshot link leaves the tracked file set")
166
+ actual = set()
167
+ for root, directories, files in os.walk(tree, followlinks=False):
168
+ for name in directories:
169
+ if (Path(root) / name).is_symlink():
170
+ raise ValueError("source snapshot cannot contain linked directories")
171
+ for name in files:
172
+ path = Path(root) / name
173
+ if not stat.S_ISREG(path.lstat().st_mode) and not path.is_symlink():
174
+ raise ValueError("source snapshot contains a special file")
175
+ actual.add(path.relative_to(tree).as_posix())
176
+ if actual != paths:
177
+ raise ValueError("source snapshot file set changed")
178
+ return source