containers: derive DCP package id from logical name

Generate the package if from logical name when it is not specified in
the json. If case it is set, use it.

Signed-off-by: Isaac Hermida <isaac.hermida@digi.com>
This commit is contained in:
Isaac Hermida 2026-04-23 10:06:44 +02:00
parent 3b93a2c2e1
commit 267f78ac9d
6 changed files with 31 additions and 27 deletions

View File

@ -68,8 +68,10 @@ 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.
- The generator appends a unique suffix to the input `package_id` using the
`created_at` timestamp encoded in base36 milliseconds.
- 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
manifest fields as:
- `<generated_package_id>_artifact_<runtime>_<device_types[0]>.tar.gz`
@ -96,9 +98,10 @@ This generates a bundle named like:
In those manifests:
- `package_id` is the base identifier used to derive the final unique DCP package ID.
- `name` is the stable logical container name stored on the target.
- `friendly_name` is the user-facing label shown by DRM and the manager output when available.
- the final `package_id` is generated automatically from `name` unless the
input manifest provides an explicit `package_id`
## Digi Remote Manager metrics support
@ -200,16 +203,18 @@ Outputs are generated in:
Final outputs:
- `${CONTAINER_PACKAGE_ID}-<base36_created_at_ms>_artifact_podman_<device_types[0]>.tar.gz`
- `${CONTAINER_PACKAGE_ID}-<base36_created_at_ms>_artifact_lxc_<device_types[0]>.tar.gz`
- `${CONTAINER_NAME}-<base36_created_at_ms>_artifact_podman_<device_types[0]>.tar.gz`
- `${CONTAINER_NAME}-<base36_created_at_ms>_artifact_lxc_<device_types[0]>.tar.gz`
Notes:
- The generator script appends a base36-encoded millisecond timestamp suffix to
the input `package_id`, stores that generated value in the final
`manifest.json`, and uses it in the DCP file name.
- In Yocto builds, `CONTAINER_PACKAGE_ID` defaults to `${CONTAINER_NAME}` before
the generator adds the unique suffix.
- 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-container` does not set `package_id`, so the
generated value always starts from `${CONTAINER_NAME}` before the generator
adds the unique suffix.
Intermediate outputs generated during the build (LXC bundle, Podman archive, OCI/rootfs files)
are removed automatically at the end of artifact creation.
@ -318,7 +323,6 @@ The artifact manifest is generated automatically and includes:
Relevant variables:
- `CONTAINER_NAME`
- `CONTAINER_PACKAGE_ID`
- `CONTAINER_FRIENDLY_NAME`
- `CONTAINER_ARTIFACT_VERSION`
- `CONTAINER_CREATE_ARGS_PODMAN`

View File

@ -1,5 +1,4 @@
{
"package_id": "flutter-demo",
"name": "flutter-demo",
"friendly_name": "Flutter Demo",
"version": "1.0",

View File

@ -1,5 +1,4 @@
{
"package_id": "flutter-demo",
"name": "flutter-demo",
"friendly_name": "Flutter Demo",
"version": "1.0",

View File

@ -47,7 +47,6 @@ do_image_container_artifacts() {
manifest_path="$2"
create_args="$3"
build_id="$4"
PACKAGE_ID="${CONTAINER_PACKAGE_ID}" \
NAME="${CONTAINER_NAME}" \
FRIENDLY_NAME="${CONTAINER_FRIENDLY_NAME}" \
VERSION="${CONTAINER_ARTIFACT_VERSION}" \
@ -69,7 +68,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 = {"package_id": os.environ["PACKAGE_ID"], "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"), "drm": {"enabled": parse_bool("DRM_ENABLED"), "stats_sample_interval_s": int(os.environ["DRM_STATS_SAMPLE_INTERVAL"]), "stats_list_of_metrics": json.loads(os.environ["DRM_STATS_LIST_OF_METRICS"])}, "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 = {"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"), "drm": {"enabled": parse_bool("DRM_ENABLED"), "stats_sample_interval_s": int(os.environ["DRM_STATS_SAMPLE_INTERVAL"]), "stats_list_of_metrics": json.loads(os.environ["DRM_STATS_LIST_OF_METRICS"])}, "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")' \

View File

@ -57,7 +57,6 @@ LXC_CONFIG_FILE ?= "${LXC_CONFIG_DIR}/config_lxc_${MACHINE}"
CONTAINER_ARTIFACT_TEMPLATE_DIR ?= ""
CONTAINER_DEFAULT_ARTIFACT_TEMPLATE_DIR ?= "${CONTAINER_PROFILE_DIR}/artifact"
META_DIGI_REPO_DIR ?= "${THISDIR}/../../.."
CONTAINER_PACKAGE_ID ?= "${CONTAINER_NAME}"
CONTAINER_FRIENDLY_NAME ?= "${CONTAINER_NAME}"
CONTAINER_ARTIFACT_VERSION ?= "${PV}"
CONTAINER_CREATE_ARGS ?= ""

View File

@ -48,8 +48,8 @@ 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_package_id: str, *, created_at_ms: int) -> str:
return f"{base_package_id}-{to_base36(created_at_ms)}"
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:
@ -139,7 +139,11 @@ def normalize_drm_stats_metrics(metrics: list[str]) -> list[str]:
def validate_manifest(data: dict) -> dict:
package_id = validate_string(data, "package_id", path="manifest")
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()
version = validate_string(data, "version", path="manifest")
runtime = validate_string(data, "runtime", path="manifest")
if runtime not in {"lxc", "podman"}:
@ -175,11 +179,7 @@ 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 = data.get("name")
if name is not None:
if not isinstance(name, str) or not name.strip():
fail("invalid manifest: name must be a non-empty string")
name = name.strip()
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():
@ -394,9 +394,13 @@ def main() -> int:
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 = build_generated_package_id(
manifest["package_id"],
created_at_ms=created_at_ms,
generated_package_id = (
manifest["package_id"]
if manifest["package_id"]
else build_generated_package_id(
manifest["name"],
created_at_ms=created_at_ms,
)
)
output_name = build_output_name(
package_id=generated_package_id,