Packer¶
xcore_agent.packer.builder — build, encrypt, and sign a .xdeploy artifact
(tar → zstd → AES-256-GCM → Ed25519).
xcore_agent.packer.builder
¶
Builds .xdeploy artifacts.
This is the build-side counterpart to agent.pipeline.DeploymentRunner,
which consumes exactly the format produced here: tar the project, zstd
-compress it, AES-256-GCM encrypt it, and sign the ciphertext with Ed25519.
Both sides call the same crypto.compute_tree_digest to produce and to
re-verify manifest.json's content_sha256, so there is no protocol drift
between "what the packer hashed" and "what the agent re-hashes".
MANIFEST_FILENAME = 'manifest.json'
module-attribute
¶
INSTALL_PLAN_PATH = 'deployment/install.yaml'
module-attribute
¶
EXTENSION_MANIFEST_FILENAMES = ('service.yaml', 'extension.yaml')
module-attribute
¶
BuildError
¶
BuildResult
dataclass
¶
Source code in xcore_agent/packer/builder.py
output_path: Path
instance-attribute
¶
manifest: ProjectManifest
instance-attribute
¶
dek: bytes
instance-attribute
¶
signature: bytes
instance-attribute
¶
signer_public_key: bytes
instance-attribute
¶
__init__(output_path: Path, manifest: ProjectManifest, dek: bytes, signature: bytes, signer_public_key: bytes) -> None
¶
build_artifact(source_root: Path, *, project_id: str, project_name: str, version: str, output_path: Path, signing_key: Ed25519PrivateKey | None = None) -> BuildResult
¶
Build, encrypt, and sign a .xdeploy artifact from source_root.
source_root must already contain a plugins directory (plugins/ by
default — see _read_plugins_dirname for how a project overrides that
via integration.yaml's plugins.directory), integration.yaml, and
deployment/install.yaml. This writes manifest.json into it (and
refuses to run if one is already there — see write_manifest). Pass
signing_key to sign with a specific, persisted Hub key; a fresh
throwaway one is generated and returned otherwise.
A plugin/extension whose manifest declares source: gets pruned down
to just its manifest file (plugin.yaml/extension.yaml) before
sealing — even if the operator's local source_root happens to have
the real code checked out at that path too (e.g. because they cloned it
to poke around, or a prior embedded build left it there). It's
resolved from git at deploy time (see plugin_resolver.py and
agent.pipeline's _resolve_plugins/_resolve_extensions), so
embedding it here would only bloat the artifact with a copy nothing
ever reads back out of it. source_root itself is never mutated —
pruning happens on a temporary copy that gets sealed and discarded.
Source code in xcore_agent/packer/builder.py
write_manifest(source_root: Path, *, project_id: str, project_name: str, version: str, plugins_dirname: str = 'plugins', plan: InstallPlan | None = None) -> ProjectManifest
¶
Compute per-plugin and whole-tree content hashes and write
manifest.json into source_root. Refuses to overwrite an existing one:
the manifest is always generated fresh from the current tree, never
hand-edited, so a leftover one is almost certainly stale.
plan is deployment/install.yaml, already parsed — pass it when
caller already has one (build_artifact does, from _load_install_
plan) to avoid re-parsing; re-read from source_root here otherwise
(e.g. a test calling write_manifest directly) if the file exists,
falling back to no install-plan sources if it doesn't. Its steps'
source: (see InstallPluginStep/InstallExtensionStep) is checked
before a plugin's own plugin.yaml source: and the xcli-written
registry — see _install_plan_plugin_sources's docstring for why.
Source code in xcore_agent/packer/builder.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | |
seal_directory(source_root: Path, *, signing_key: Ed25519PrivateKey | None = None) -> tuple[bytes, bytes, bytes, bytes]
¶
Tar, zstd-compress, AES-256-GCM encrypt, and Ed25519-sign
source_root as-is. Pure packaging — does not touch or require
manifest.json, so it can also be used to build a deliberately
tampered artifact for tests.
Returns (ciphertext, dek, signature, signer_public_key). ciphertext is
a 12-byte nonce prefix followed by the AES-256-GCM ciphertext, matching
what agent.pipeline.DeploymentRunner._decrypt expects.