diff --git a/internal/common/tasks/scripts/build_image.sh b/internal/common/tasks/scripts/build_image.sh index 85230d30..9987c7a6 100644 --- a/internal/common/tasks/scripts/build_image.sh +++ b/internal/common/tasks/scripts/build_image.sh @@ -296,6 +296,9 @@ if [ -z "$BUILDER_IMAGE" ] && { [ "$BUILD_MODE" = "bootc" ] || [ "$BUILD_MODE" = echo "Using builder image from cluster registry: $BUILDER_IMAGE" fi +# Record the effective builder image used for annotation +echo -n "${BUILDER_IMAGE:-}" > /tekton/results/builder-image + if [ -n "$BUILDER_IMAGE" ] && { [ "$BUILD_MODE" = "bootc" ] || [ "$BUILD_MODE" = "disk" ]; }; then echo "Pulling builder image to local storage: $BUILDER_IMAGE" @@ -370,11 +373,42 @@ case "$BUILD_MODE" in "${DISK_OUTPUT_ARGS[@]}" if [ -n "$CONTAINER_PUSH" ]; then + PUSH_SRC="containers-storage:$BOOTC_CONTAINER_NAME" + + # Add builder-image as manifest annotation + config label + if [ -n "$BUILDER_IMAGE" ]; then + echo "Annotating bootc container with builder image: $BUILDER_IMAGE" + OCI_DIR="/tmp/bootc-oci" + rm -rf "$OCI_DIR" + skopeo copy "$PUSH_SRC" "oci:${OCI_DIR}:latest" + python3 - "$OCI_DIR" "$BUILDER_IMAGE" <<'PYEOF' +import json, sys, hashlib, os +KEY = "automotive.sdv.cloud.redhat.com/builder-image" +def update_blob(d, old_dig, data): + content = json.dumps(data, indent=2).encode() + h = hashlib.sha256(content).hexdigest() + new_p = os.path.join(d, "blobs", "sha256", h) + old_p = os.path.join(d, "blobs", *old_dig.split(":", 1)) + with open(new_p, "wb") as f: f.write(content) + if old_p != new_p: os.remove(old_p) + return f"sha256:{h}", len(content) +d, val = sys.argv[1], sys.argv[2] +with open(os.path.join(d, "index.json")) as f: index = json.load(f) +entry = index["manifests"][0] +with open(os.path.join(d, "blobs", *entry["digest"].split(":", 1))) as f: manifest = json.load(f) +with open(os.path.join(d, "blobs", *manifest["config"]["digest"].split(":", 1))) as f: config = json.load(f) +config.setdefault("config", {}).setdefault("Labels", {})[KEY] = val +manifest["config"]["digest"], manifest["config"]["size"] = update_blob(d, manifest["config"]["digest"], config) +manifest.setdefault("annotations", {})[KEY] = val +entry["digest"], entry["size"] = update_blob(d, entry["digest"], manifest) +with open(os.path.join(d, "index.json"), "w") as f: json.dump(index, f, indent=2) +PYEOF + PUSH_SRC="oci:${OCI_DIR}:latest" + fi + echo "Pushing container to registry: $CONTAINER_PUSH" - skopeo copy \ - --authfile="$REGISTRY_AUTH_FILE" \ - "containers-storage:$BOOTC_CONTAINER_NAME" \ - "docker://$CONTAINER_PUSH" + skopeo copy --authfile="$REGISTRY_AUTH_FILE" "$PUSH_SRC" "docker://$CONTAINER_PUSH" + rm -rf "${OCI_DIR:-/tmp/nonexistent}" 2>/dev/null || true echo "Container pushed successfully to $CONTAINER_PUSH" fi diff --git a/internal/common/tasks/scripts/push_artifact.sh b/internal/common/tasks/scripts/push_artifact.sh index 0198e2d5..84cd50a3 100644 --- a/internal/common/tasks/scripts/push_artifact.sh +++ b/internal/common/tasks/scripts/push_artifact.sh @@ -173,6 +173,7 @@ parts_dir="${exportFile}-parts" distro="$(params.distro)" target="$(params.target)" arch="$(params.arch)" +builder_image_used="$(params.builder-image)" config_file="/etc/partition-config/partition-rules.yaml" default_partitions="" @@ -196,6 +197,13 @@ if [ -n "$default_partitions" ]; then \"automotive.sdv.cloud.redhat.com/default-partitions\": \"${default_partitions_escaped}\"" fi +builder_image_annotation="" +if [ -n "$builder_image_used" ]; then + builder_image_escaped=$(json_escape "$builder_image_used") + builder_image_annotation=", + \"automotive.sdv.cloud.redhat.com/builder-image\": \"${builder_image_escaped}\"" +fi + cd /workspace/shared echo "=== Artifact Push Configuration ===" @@ -298,7 +306,7 @@ if [ -d "${parts_dir}" ] && [ -n "$(ls -A "${parts_dir}" 2>/dev/null)" ]; then "automotive.sdv.cloud.redhat.com/parts": "${file_list}", "automotive.sdv.cloud.redhat.com/distro": "${distro}", "automotive.sdv.cloud.redhat.com/target": "${target}", - "automotive.sdv.cloud.redhat.com/arch": "${arch}"${default_partitions_annotation} + "automotive.sdv.cloud.redhat.com/arch": "${arch}"${default_partitions_annotation}${builder_image_annotation} }, ${layer_annotations_json} } @@ -348,6 +356,10 @@ else fi fi + if [ -n "$builder_image_used" ]; then + annotation_args="${annotation_args} --annotation automotive.sdv.cloud.redhat.com/builder-image=${builder_image_used}" + fi + echo "Pushing single-file artifact to ${repo_url}" echo " File: ${exportFile}" echo " Media type: ${media_type}" diff --git a/internal/common/tasks/tasks.go b/internal/common/tasks/tasks.go index d4865232..c7c0e3d5 100644 --- a/internal/common/tasks/tasks.go +++ b/internal/common/tasks/tasks.go @@ -80,6 +80,15 @@ func GeneratePushArtifactRegistryTask(namespace string) *tektonv1.Task { Type: tektonv1.ParamTypeString, Description: "Filename of the artifact to push", }, + { + Name: "builder-image", + Type: tektonv1.ParamTypeString, + Description: "The builder image used for the build", + Default: &tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: "", + }, + }, }, Workspaces: []tektonv1.WorkspaceDeclaration{ { @@ -263,6 +272,10 @@ func GenerateBuildAutomotiveImageTask(namespace string, buildConfig *BuildConfig Name: "artifact-filename", Description: "artifact filename placed in the shared workspace", }, + { + Name: "builder-image", + Description: "The builder image used for the build", + }, }, Workspaces: []tektonv1.WorkspaceDeclaration{ { @@ -667,6 +680,11 @@ func GenerateTektonPipeline(name, namespace string) *tektonv1.Pipeline { Description: "The final artifact filename produced by the build", Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.build-image.results.artifact-filename)"}, }, + { + Name: "builder-image", + Description: "The builder image reference used for the build", + Value: tektonv1.ParamValue{Type: tektonv1.ParamTypeString, StringVal: "$(tasks.build-image.results.builder-image)"}, + }, { Name: "lease-id", Description: "The Jumpstarter lease ID acquired during flash (empty if flash not enabled)", @@ -969,6 +987,13 @@ func GenerateTektonPipeline(name, namespace string) *tektonv1.Pipeline { StringVal: "$(tasks.build-image.results.artifact-filename)", }, }, + { + Name: "builder-image", + Value: tektonv1.ParamValue{ + Type: tektonv1.ParamTypeString, + StringVal: "$(tasks.build-image.results.builder-image)", + }, + }, }, Workspaces: []tektonv1.WorkspacePipelineTaskBinding{ {Name: "shared-workspace", Workspace: "shared-workspace"}, diff --git a/internal/controller/imagebuild/controller.go b/internal/controller/imagebuild/controller.go index 32486f56..fe412413 100644 --- a/internal/controller/imagebuild/controller.go +++ b/internal/controller/imagebuild/controller.go @@ -1354,9 +1354,9 @@ func taskRunFailureMessage(taskRun *tektonv1.TaskRun, fallback string) string { func extractProvenance(pipelineRun *tektonv1.PipelineRun, aibImage string) (aibImageUsed, builderImageUsed string) { aibImageUsed = aibImage // Always record the AIB image that was requested - // Extract builder image from prepare-builder task result + // Extract builder image from pipeline result (written by build-image task) for _, result := range pipelineRun.Status.Results { - if result.Name == "builder-image-ref" { + if result.Name == "builder-image" { builderImageUsed = result.Value.StringVal break }