meta-digi-containers: modify how to generate the DCP
Modify and document the way to generate a DCP by package_id. Update the source code of the container manager to align with. Signed-off-by: Isaac Hermida <isaac.hermida@digi.com>
This commit is contained in:
parent
9de4842dc3
commit
7950ac2460
|
|
@ -78,13 +78,11 @@ The script generates exactly one runtime artifact per execution.
|
|||
Notes:
|
||||
|
||||
- `--output-dir` is optional. If omitted, the script writes the DCP to the current directory.
|
||||
- If the input manifest omits `package_id`, the generator derives the final
|
||||
`package_id` from `name` and appends a unique suffix using the `created_at`
|
||||
timestamp encoded in base36 milliseconds.
|
||||
- If the input manifest provides `package_id`, that value is kept unchanged.
|
||||
- The output file name is always derived from the generated package ID and
|
||||
- The input manifest must provide `package_id`, which becomes the stable target
|
||||
identifier.
|
||||
- The output file name is always derived from the package ID and
|
||||
manifest fields as:
|
||||
- `<generated_package_id>-<runtime>-<device_types[0]>.tar.gz`
|
||||
- `<package_id>-<runtime>-<device_types[0]>.tar.gz`
|
||||
- For Podman payloads, the final internal payload name is always `payload/image.tar`
|
||||
- For LXC payloads, the generator requires a Yocto-style `.tar.gz` bundle, validates its layout,
|
||||
and stores it inside the DCP using the original file name and format without re-packaging it.
|
||||
|
|
@ -104,14 +102,13 @@ python3 meta-digi-containers/scripts/generate-dcp.py \
|
|||
|
||||
This generates a bundle named like:
|
||||
|
||||
- `./flutter-demo-<base36_created_at_ms>-podman-ccmp25-dvk.tar.gz`
|
||||
- `./flutter-demo-podman-ccmp25-dvk.tar.gz`
|
||||
|
||||
In those manifests:
|
||||
|
||||
- `name` is the stable logical container name stored on the target.
|
||||
- `package_id` is the stable target identifier.
|
||||
- `friendly_name` is the user-facing label shown by the manager output when available.
|
||||
- the final `package_id` is generated automatically from `name` unless the
|
||||
input manifest provides an explicit `package_id`
|
||||
- the final DCP manifest does not include a second container identifier
|
||||
- `registration_defaults` is limited to local manager policy such as `autostart`,
|
||||
`monitor`, and `restart`; this release does not generate DRM-specific defaults
|
||||
|
||||
|
|
@ -182,19 +179,12 @@ Outputs are generated in:
|
|||
|
||||
Final outputs:
|
||||
|
||||
- `${DCP_NAME}-<base36_created_at_ms>-podman-<device_types[0]>.tar.gz`
|
||||
- `${DCP_NAME}-<base36_created_at_ms>-lxc-<device_types[0]>.tar.gz`
|
||||
- `${DCP_NAME}-podman-<device_types[0]>.tar.gz`
|
||||
- `${DCP_NAME}-lxc-<device_types[0]>.tar.gz`
|
||||
|
||||
Notes:
|
||||
|
||||
- The generator script derives `package_id` from the input `name` only when the
|
||||
manifest does not provide one explicitly. In that default case it appends a
|
||||
base36-encoded millisecond timestamp suffix, stores the generated `package_id`
|
||||
in the final `manifest.json`, and uses it in the DCP file name.
|
||||
- In Yocto builds, `dey-image-dcp` keeps that unique-name behavior and
|
||||
removes older DCP artifacts with the same `${DCP_NAME}` prefix before
|
||||
generating the new one, so the deploy directory does not keep accumulating
|
||||
previous builds of the same container/runtime.
|
||||
- In Yocto builds, `dey-image-dcp` sets `package_id` to `${DCP_NAME}`.
|
||||
|
||||
Intermediate rootfs and OCI outputs are kept available for incremental rebuilds.
|
||||
The LXC bundle and Podman archive are generated as temporary payloads while
|
||||
|
|
@ -281,7 +271,6 @@ Supported placeholders in LXC config fragments:
|
|||
The artifact manifest is generated automatically and includes:
|
||||
|
||||
- `package_id`
|
||||
- `name` [stable logical container name]
|
||||
- `friendly_name` [optional user-facing display name]
|
||||
- `version`
|
||||
- `runtime`
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "flutter-demo",
|
||||
"package_id": "flutter-demo",
|
||||
"friendly_name": "Flutter Demo",
|
||||
"version": "1.0",
|
||||
"runtime": "lxc",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "flutter-demo",
|
||||
"package_id": "flutter-demo",
|
||||
"friendly_name": "Flutter Demo",
|
||||
"version": "1.0",
|
||||
"runtime": "podman",
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ SRC_URI = " \
|
|||
file://cc-containerd.service \
|
||||
file://cc-containerd-shutdown.service \
|
||||
"
|
||||
SRC_URI[archive.md5sum] = "6d84c6f5ec9dc94d542c91001ff5fd36"
|
||||
SRC_URI[archive.sha256sum] = "ce24c4fde041a69a7646eb9bad4891d2eb91291f3534e71444552d3830247aaa"
|
||||
SRC_URI[archive.md5sum] = "2ae2c3c09e9bf223e7de4ec0994376f3"
|
||||
SRC_URI[archive.sha256sum] = "627d90eb53a48bf978fb6993f661af3dba8e6bf091d295ff481457e4c0cb96a0"
|
||||
|
||||
S = "${WORKDIR}/${BP}"
|
||||
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ do_clean_old_container_artifacts() {
|
|||
fi
|
||||
|
||||
for runtime in lxc podman; do
|
||||
old_artifact_glob="${DEPLOY_DIR_IMAGE}/${DCP_NAME}-*-${runtime}-${primary_device_type}.tar.gz"
|
||||
bbnote "Removing old ${runtime} container artifacts matching ${old_artifact_glob}"
|
||||
rm -f ${old_artifact_glob}
|
||||
artifact_path="${DEPLOY_DIR_IMAGE}/${DCP_NAME}-${runtime}-${primary_device_type}.tar.gz"
|
||||
bbnote "Removing old ${runtime} container artifact ${artifact_path}"
|
||||
rm -f "${artifact_path}"
|
||||
done
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ do_image_container_artifacts() {
|
|||
manifest_path="$2"
|
||||
create_args="$3"
|
||||
build_id="$4"
|
||||
NAME="${DCP_NAME}" \
|
||||
PACKAGE_ID="${DCP_NAME}" \
|
||||
FRIENDLY_NAME="${CONTAINER_FRIENDLY_NAME}" \
|
||||
VERSION="${CONTAINER_ARTIFACT_VERSION}" \
|
||||
RUNTIME="${runtime}" \
|
||||
|
|
@ -87,7 +87,7 @@ do_image_container_artifacts() {
|
|||
LABELS_JSON="${CONTAINER_ARTIFACT_LABELS_JSON}" \
|
||||
python3 -c 'import json, os, sys; \
|
||||
parse_bool = lambda name: os.environ[name].strip().lower() == "true"; \
|
||||
payload = {"name": os.environ["NAME"], "friendly_name": os.environ["FRIENDLY_NAME"], "version": os.environ["VERSION"], "runtime": os.environ["RUNTIME"], "registration_defaults": {"autostart": parse_bool("AUTOSTART"), "monitor": parse_bool("MONITOR"), "restart": {"enabled": parse_bool("RESTART_ENABLED"), "max_retries": int(os.environ["RESTART_MAX_RETRIES"]), "window": int(os.environ["RESTART_WINDOW"]), "retry_delay": int(os.environ["RESTART_RETRY_DELAY"]) } }, "device_types": json.loads(os.environ["DEVICE_TYPES_JSON"]), "firmware_versions": os.environ["FIRMWARE_VERSIONS"], "build_id": os.environ["BUILD_ID"], "description": os.environ["DESCRIPTION"], "labels": json.loads(os.environ["LABELS_JSON"])}; \
|
||||
payload = {"package_id": os.environ["PACKAGE_ID"], "friendly_name": os.environ["FRIENDLY_NAME"], "version": os.environ["VERSION"], "runtime": os.environ["RUNTIME"], "registration_defaults": {"autostart": parse_bool("AUTOSTART"), "monitor": parse_bool("MONITOR"), "restart": {"enabled": parse_bool("RESTART_ENABLED"), "max_retries": int(os.environ["RESTART_MAX_RETRIES"]), "window": int(os.environ["RESTART_WINDOW"]), "retry_delay": int(os.environ["RESTART_RETRY_DELAY"]) } }, "device_types": json.loads(os.environ["DEVICE_TYPES_JSON"]), "firmware_versions": os.environ["FIRMWARE_VERSIONS"], "build_id": os.environ["BUILD_ID"], "description": os.environ["DESCRIPTION"], "labels": json.loads(os.environ["LABELS_JSON"])}; \
|
||||
create_args = os.environ["CREATE_ARGS"].strip(); \
|
||||
payload.update({"create_args": create_args} if create_args else {}); \
|
||||
open(sys.argv[1], "w", encoding="utf-8").write(json.dumps(payload, indent=2) + "\n")' \
|
||||
|
|
|
|||
|
|
@ -15,32 +15,15 @@ import sys
|
|||
import tarfile
|
||||
import tempfile
|
||||
|
||||
BASE36_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
def fail(message: str) -> "NoReturn":
|
||||
raise SystemExit(f"error: {message}")
|
||||
|
||||
|
||||
def to_base36(value: int) -> str:
|
||||
if value < 0:
|
||||
fail("cannot convert negative values to base36")
|
||||
if value == 0:
|
||||
return "0"
|
||||
result: list[str] = []
|
||||
while value:
|
||||
value, remainder = divmod(value, 36)
|
||||
result.append(BASE36_ALPHABET[remainder])
|
||||
return "".join(reversed(result))
|
||||
|
||||
|
||||
def format_created_at(timestamp: datetime) -> str:
|
||||
return timestamp.astimezone(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
|
||||
|
||||
def build_generated_package_id(base_name: str, *, created_at_ms: int) -> str:
|
||||
return f"{base_name}-{to_base36(created_at_ms)}"
|
||||
|
||||
|
||||
def load_manifest(path: Path) -> dict:
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
|
@ -100,11 +83,7 @@ def validate_labels(value: object) -> dict:
|
|||
|
||||
|
||||
def validate_manifest(data: dict) -> dict:
|
||||
package_id = data.get("package_id")
|
||||
if package_id is not None:
|
||||
if not isinstance(package_id, str) or not package_id.strip():
|
||||
fail("invalid manifest: package_id must be a non-empty string")
|
||||
package_id = package_id.strip()
|
||||
package_id = validate_string(data, "package_id", path="manifest")
|
||||
version = validate_string(data, "version", path="manifest")
|
||||
runtime = validate_string(data, "runtime", path="manifest")
|
||||
if runtime not in {"lxc", "podman"}:
|
||||
|
|
@ -136,7 +115,6 @@ def validate_manifest(data: dict) -> dict:
|
|||
description = data.get("description", "")
|
||||
if description is not None and not isinstance(description, str):
|
||||
fail("invalid manifest: description must be a string")
|
||||
name = validate_string(data, "name", path="manifest")
|
||||
friendly_name = data.get("friendly_name")
|
||||
if friendly_name is not None:
|
||||
if not isinstance(friendly_name, str) or not friendly_name.strip():
|
||||
|
|
@ -145,7 +123,6 @@ def validate_manifest(data: dict) -> dict:
|
|||
|
||||
validated = {
|
||||
"package_id": package_id,
|
||||
"name": name,
|
||||
"friendly_name": friendly_name,
|
||||
"version": version,
|
||||
"runtime": runtime,
|
||||
|
|
@ -269,7 +246,6 @@ def build_final_manifest(
|
|||
) -> dict:
|
||||
output = {
|
||||
"package_id": package_id,
|
||||
"name": base["name"],
|
||||
"friendly_name": base["friendly_name"],
|
||||
"version": base["version"],
|
||||
"runtime": base["runtime"],
|
||||
|
|
@ -332,18 +308,10 @@ def main() -> int:
|
|||
fail(f"cannot create output directory {output_dir}: {exc}")
|
||||
|
||||
created_at_dt = datetime.now(timezone.utc)
|
||||
created_at_ms = int(created_at_dt.timestamp() * 1000)
|
||||
created_at = format_created_at(created_at_dt)
|
||||
generated_package_id = (
|
||||
manifest["package_id"]
|
||||
if manifest["package_id"]
|
||||
else build_generated_package_id(
|
||||
manifest["name"],
|
||||
created_at_ms=created_at_ms,
|
||||
)
|
||||
)
|
||||
package_id = manifest["package_id"]
|
||||
output_name = build_output_name(
|
||||
package_id=generated_package_id,
|
||||
package_id=package_id,
|
||||
runtime=manifest["runtime"],
|
||||
device_type=manifest["device_types"][0],
|
||||
)
|
||||
|
|
@ -375,7 +343,7 @@ def main() -> int:
|
|||
|
||||
final_manifest = build_final_manifest(
|
||||
manifest,
|
||||
package_id=generated_package_id,
|
||||
package_id=package_id,
|
||||
artifact_type=artifact_type,
|
||||
digest=digest,
|
||||
size_bytes=size_bytes,
|
||||
|
|
@ -389,7 +357,7 @@ def main() -> int:
|
|||
lambda dst: write_default_readme(
|
||||
dst,
|
||||
manifest,
|
||||
package_id=generated_package_id,
|
||||
package_id=package_id,
|
||||
created_at=created_at,
|
||||
payload_name=payload_name,
|
||||
),
|
||||
|
|
|
|||
Loading…
Reference in New Issue