1#! /bin/sh 2 3# Build an Open Container Initiative (OCI) container image 4 5curdir=$1; shift 6rev=$1; shift 7branch=$1; shift 8arch=$1; shift 9image=$1; shift 10output=$1; shift 11 12major=${rev%.*} 13minor=${rev#*.} 14 15abi=FreeBSD:${major}:${arch} 16ver=${rev}-${branch}-${arch} 17 18echo "Building OCI freebsd${major}-${image} image for ${abi}" 19 20. ${curdir}/tools/oci-image-${image}.conf 21 22init_repo() { 23 local workdir=$1; shift 24 local abi=$1; shift 25 local srcdir=$(realpath ${curdir}/..) 26 27 mkdir -p ${workdir}/repos 28 cat > ${workdir}/repos/base.conf <<EOF 29FreeBSD-base: { 30 url: "file:///usr/obj${srcdir}/repo/${abi}/latest" 31 signature_type: "none" 32 fingerprints: "none" 33} 34EOF 35} 36 37# Install packages using pkg(8) into a container with rootfs at $3 38install_packages() { 39 local abi=$1; shift 40 local workdir=$1; shift 41 local rootdir=${workdir}/rootfs 42 43 # Make sure we have the keys needed for verifying package integrity if 44 # not already added by a parent image. 45 if [ ! -d ${rootdir}/usr/share/keys/pkg/trusted ]; then 46 mkdir -p ${rootdir}/usr/share/keys/pkg/trusted 47 fi 48 for i in ${curdir}/../share/keys/pkg/trusted/pkg.*; do 49 if [ ! -f ${rootdir}/usr/share/keys/pkg/trusted/$(basename $i) ]; then 50 cp $i ${rootdir}/usr/share/keys/pkg/trusted 51 fi 52 done 53 54 # We install the packages and then remove repository metadata (keeping the 55 # metadata for what was installed). This trims more than 40Mb from the 56 # resulting image. 57 env IGNORE_OSVERSION=yes ABI=${abi} pkg --rootdir ${rootdir} --repo-conf-dir ${workdir}/repos \ 58 install -yq -g "$@" || exit $? 59 rm -rf ${rootdir}/var/db/pkg/repos 60} 61 62set_cmd() { 63 local workdir=$1; shift 64 oci_cmd="$@" 65} 66 67# Convert FreeBSD architecture to OCI-style. See 68# https://github.com/containerd/platforms/blob/main/platforms.go for details 69normalize_arch() { 70 local arch=$1; shift 71 case ${arch} in 72 i386) 73 arch=386 74 ;; 75 aarch64) 76 arch=arm64 77 ;; 78 amd64) ;; 79 riscv64) ;; 80 *) 81 echo "Architecture ${arch} not supported for container images" 82 ;; 83 esac 84 echo ${arch} 85} 86 87create_container() { 88 local workdir=$1; shift 89 local base_workdir=$1; shift 90 oci_cmd= 91 if [ -d ${workdir}/rootfs ]; then 92 chflags -R 0 ${workdir}/rootfs 93 rm -rf ${workdir}/rootfs 94 fi 95 mkdir -p ${workdir}/rootfs 96 if [ "${base_workdir}" != "" ]; then 97 tar -C ${workdir}/rootfs -xf ${base_workdir}/rootfs.tar.gz 98 fi 99} 100 101commit_container() { 102 local workdir=$1; shift 103 local image=$1; shift 104 local output=$1; shift 105 106 # Note: the diff_id (needed for image config) is the hash of the 107 # uncompressed tar. 108 # 109 # For compatibility with Podman, we must disable sparse-file 110 # handling. See https://github.com/containers/podman/issues/25270 for 111 # more details. 112 tar -C ${workdir}/rootfs --strip-components 1 --no-read-sparse -cf ${workdir}/rootfs.tar . 113 local diff_id=$(sha256 -q < ${workdir}/rootfs.tar) 114 gzip -f ${workdir}/rootfs.tar 115 local create_time=$(date -u +%Y-%m-%dT%TZ) 116 local root_hash=$(sha256 -q < ${workdir}/rootfs.tar.gz) 117 local root_size=$(stat -f %z ${workdir}/rootfs.tar.gz) 118 119 oci_arch=$(normalize_arch ${arch}) 120 121 config= 122 if [ -n "${oci_cmd}" ]; then 123 config=",\"config\":{\"cmd\":[\"${oci_cmd}\"]}" 124 fi 125 echo "{\"created\":\"${create_time}\",\"architecture\":\"${oci_arch}\",\"os\":\"freebsd\"${config},\"rootfs\":{\"type\":\"layers\",\"diff_ids\":[\"sha256:${diff_id}\"]},\"history\":[{\"created\":\"${create_time}\",\"created_by\":\"make-oci-image.sh\"}]}" > ${workdir}/config.json 126 local config_hash=$(sha256 -q < ${workdir}/config.json) 127 local config_size=$(stat -f %z ${workdir}/config.json) 128 129 echo "{\"schemaVersion\":2,\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"config\":{\"mediaType\":\"application/vnd.oci.image.config.v1+json\",\"digest\":\"sha256:${config_hash}\",\"size\":${config_size}},\"layers\":[{\"mediaType\":\"application/vnd.oci.image.layer.v1.tar+gzip\",\"digest\":\"sha256:${root_hash}\",\"size\":${root_size}}],\"annotations\":{}}" > ${workdir}/manifest.json 130 local manifest_hash=$(sha256 -q < ${workdir}/manifest.json) 131 local manifest_size=$(stat -f %z ${workdir}/manifest.json) 132 133 mkdir -p ${workdir}/oci/blobs/sha256 134 echo "{\"imageLayoutVersion\": \"1.0.0\"}" > ${workdir}/oci/oci-layout 135 echo "{\"schemaVersion\":2,\"manifests\":[{\"mediaType\":\"application/vnd.oci.image.manifest.v1+json\",\"digest\":\"sha256:${manifest_hash}\",\"size\":${manifest_size},\"annotations\":{\"org.opencontainers.image.ref.name\":\"freebsd-${image}:${ver}\"}}]}" > ${workdir}/oci/index.json 136 ln ${workdir}/rootfs.tar.gz ${workdir}/oci/blobs/sha256/${root_hash} 137 ln ${workdir}/config.json ${workdir}/oci/blobs/sha256/${config_hash} 138 ln ${workdir}/manifest.json ${workdir}/oci/blobs/sha256/${manifest_hash} 139 140 tar -C ${workdir}/oci --xz --strip-components 1 --no-read-sparse -a -cf ${output} . 141} 142 143# Prefix with "container-image-" so that we can create a unique work area under 144# ${.OBJDIR}. We can assume that make has set our working directory to 145# ${.OBJDIR}. 146workdir=${PWD}/container-image-${image} 147init_repo ${workdir} ${abi} 148 149if [ -n "${OCI_BASE_IMAGE}" ]; then 150 base_workdir=${PWD}/container-image-${OCI_BASE_IMAGE} 151else 152 base_workdir= 153fi 154 155create_container ${workdir} ${base_workdir} 156oci_image_build 157commit_container ${workdir} ${image} ${output} 158