Generate unique DCP package ids
https://onedigi.atlassian.net/browse/DEL-10035 https://onedigi.atlassian.net/browse/DEL-10081 Signed-off-by: Isaac Hermida <isaac.hermida@digi.com>
This commit is contained in:
parent
d743784281
commit
69286f4057
|
|
@ -68,8 +68,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.
|
||||
- The output file name is always derived from manifest fields as:
|
||||
- `<package_id>_artifact_<runtime>_<device_types[0]>.tar.gz`
|
||||
- The generator appends a unique suffix to the input `package_id` using the
|
||||
`created_at` timestamp encoded in base36 milliseconds.
|
||||
- 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`
|
||||
- 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.
|
||||
|
|
@ -87,9 +90,9 @@ python3 meta-digi-containers/scripts/generate-dcp.py \
|
|||
--payload /tmp/image.tar
|
||||
```
|
||||
|
||||
This generates:
|
||||
This generates a bundle named like:
|
||||
|
||||
- `./flutter-demo_artifact_podman_ccmp25-dvk.tar.gz`
|
||||
- `./flutter-demo-<base36_created_at_ms>_artifact_podman_ccmp25-dvk.tar.gz`
|
||||
|
||||
## Digi Remote Manager metrics support
|
||||
|
||||
|
|
@ -97,6 +100,10 @@ This generates:
|
|||
publish container statistics through the local CCCS Python API.
|
||||
For generated DCPs, per-container DRM sampling is enabled
|
||||
through `registration_defaults.stats_publish` in the artifact manifest.
|
||||
Those manifest defaults establish the initial runtime policy on the target.
|
||||
After installation, mutable policy such as `autostart`, `monitor`, `restart`, and
|
||||
`stats_publish` can be inspected or updated through the container manager `config`
|
||||
`get` and `set` operations without regenerating the DCP.
|
||||
The image recipe generates the DCP automatically from the following variables:
|
||||
|
||||
- `CONTAINER_STATS_PUBLISH_ENABLED`
|
||||
|
|
@ -169,14 +176,16 @@ Outputs are generated in:
|
|||
|
||||
Final outputs:
|
||||
|
||||
- `${CONTAINER_PACKAGE_ID}_artifact_podman_<device_types[0]>.tar.gz`
|
||||
- `${CONTAINER_PACKAGE_ID}_artifact_lxc_<device_types[0]>.tar.gz`
|
||||
- `${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`
|
||||
|
||||
Notes:
|
||||
|
||||
- The generator script makes the DCP filename using `package_id`, `runtime`, and
|
||||
`device_types[0]` fields from the manifest file.
|
||||
- In Yocto builds, `CONTAINER_PACKAGE_ID` defaults to `${CONTAINER_NAME}`
|
||||
- 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.
|
||||
|
||||
Intermediate outputs generated during the build (LXC bundle, Podman archive, OCI/rootfs files)
|
||||
are removed automatically at the end of artifact creation.
|
||||
|
|
|
|||
|
|
@ -109,14 +109,14 @@ open(sys.argv[1], "w", encoding="utf-8").write(json.dumps(payload, indent=2) + "
|
|||
changelog_arg="--changelog ${template_dir}/metadata/changelog.txt"
|
||||
fi
|
||||
|
||||
python3 "${generator_script}" \
|
||||
generated_output="$(python3 "${generator_script}" \
|
||||
--manifest "${manifest_path}" \
|
||||
--payload "${src_payload}" \
|
||||
--output-dir "${DEPLOY_DIR_IMAGE}" \
|
||||
${readme_arg} \
|
||||
${changelog_arg}
|
||||
${changelog_arg})"
|
||||
|
||||
generated_output="${DEPLOY_DIR_IMAGE}/${CONTAINER_PACKAGE_ID}_artifact_${runtime}_${primary_device_type}.tar.gz"
|
||||
generated_output="$(printf '%s' "${generated_output}" | tail -n 1)"
|
||||
if [ ! -s "${generated_output}" ]; then
|
||||
bbfatal "Container artifact bundle not generated correctly: ${generated_output}"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -15,11 +15,33 @@ 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_package_id: str, *, created_at_ms: int) -> str:
|
||||
return f"{base_package_id}-{to_base36(created_at_ms)}"
|
||||
|
||||
|
||||
def load_manifest(path: Path) -> dict:
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
|
@ -167,8 +189,8 @@ def sha256(path: Path) -> str:
|
|||
return digest.hexdigest()
|
||||
|
||||
|
||||
def build_output_name(manifest: dict) -> str:
|
||||
return f"{manifest['package_id']}_artifact_{manifest['runtime']}_{manifest['device_types'][0]}.tar.gz"
|
||||
def build_output_name(*, package_id: str, runtime: str, device_type: str) -> str:
|
||||
return f"{package_id}_artifact_{runtime}_{device_type}.tar.gz"
|
||||
|
||||
|
||||
def ensure_lxc_layout(payload: Path) -> tuple[str, str]:
|
||||
|
|
@ -188,9 +210,16 @@ def ensure_lxc_layout(payload: Path) -> tuple[str, str]:
|
|||
return dirs[0].name, dirs[0].name
|
||||
|
||||
fail("invalid payload: LXC archive must contain rootfs/ and config")
|
||||
def write_default_readme(path: Path, manifest: dict, *, created_at: str, payload_name: str) -> None:
|
||||
def write_default_readme(
|
||||
path: Path,
|
||||
manifest: dict,
|
||||
*,
|
||||
package_id: str,
|
||||
created_at: str,
|
||||
payload_name: str,
|
||||
) -> None:
|
||||
content = (
|
||||
f"Package: {manifest['package_id']}\n"
|
||||
f"Package: {package_id}\n"
|
||||
f"Version: {manifest['version']}\n"
|
||||
f"Runtime: {manifest['runtime']}\n"
|
||||
f"Platform: {manifest['device_types'][0]}\n"
|
||||
|
|
@ -218,9 +247,17 @@ def write_manifest(path: Path, manifest: dict) -> None:
|
|||
path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def build_final_manifest(base: dict, *, artifact_type: str, digest: str, size_bytes: int, created_at: str) -> dict:
|
||||
def build_final_manifest(
|
||||
base: dict,
|
||||
*,
|
||||
package_id: str,
|
||||
artifact_type: str,
|
||||
digest: str,
|
||||
size_bytes: int,
|
||||
created_at: str,
|
||||
) -> dict:
|
||||
output = {
|
||||
"package_id": base["package_id"],
|
||||
"package_id": package_id,
|
||||
"version": base["version"],
|
||||
"runtime": base["runtime"],
|
||||
"artifact_type": artifact_type,
|
||||
|
|
@ -281,9 +318,19 @@ def main() -> int:
|
|||
except OSError as exc:
|
||||
fail(f"cannot create output directory {output_dir}: {exc}")
|
||||
|
||||
output_name = build_output_name(manifest)
|
||||
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,
|
||||
)
|
||||
output_name = build_output_name(
|
||||
package_id=generated_package_id,
|
||||
runtime=manifest["runtime"],
|
||||
device_type=manifest["device_types"][0],
|
||||
)
|
||||
output_path = output_dir / output_name
|
||||
created_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="dcp-bundle-") as tmpdir:
|
||||
staging_root = Path(tmpdir)
|
||||
|
|
@ -311,6 +358,7 @@ def main() -> int:
|
|||
|
||||
final_manifest = build_final_manifest(
|
||||
manifest,
|
||||
package_id=generated_package_id,
|
||||
artifact_type=artifact_type,
|
||||
digest=digest,
|
||||
size_bytes=size_bytes,
|
||||
|
|
@ -321,7 +369,13 @@ def main() -> int:
|
|||
stage_metadata(
|
||||
readme_path,
|
||||
metadata_dir / "README.txt",
|
||||
lambda dst: write_default_readme(dst, manifest, created_at=created_at, payload_name=payload_name),
|
||||
lambda dst: write_default_readme(
|
||||
dst,
|
||||
manifest,
|
||||
package_id=generated_package_id,
|
||||
created_at=created_at,
|
||||
payload_name=payload_name,
|
||||
),
|
||||
)
|
||||
stage_metadata(changelog_path, metadata_dir / "changelog.txt", write_default_changelog)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue