Skip to content

Commit 23ced5f

Browse files
committed
cmd-osbuild: add foundation for what will be cosa osbuild
This is a direct copy of cmd-buildextend-metal for now. Will update it to be functional in the next commit.
1 parent d35471a commit 23ced5f

File tree

2 files changed

+337
-1
lines changed

2 files changed

+337
-1
lines changed

cmd/coreos-assembler.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
// commands we'd expect to use in the local dev path
15-
var buildCommands = []string{"init", "fetch", "build", "run", "prune", "clean", "list"}
15+
var buildCommands = []string{"init", "fetch", "build", "osbuild", "run", "prune", "clean", "list"}
1616
var advancedBuildCommands = []string{"buildfetch", "buildupload", "oc-adm-release", "push-container"}
1717
var buildextendCommands = []string{"aliyun", "applehv", "aws", "azure", "digitalocean", "exoscale", "extensions-container", "gcp", "hyperv", "ibmcloud", "kubevirt", "live", "metal", "metal4k", "nutanix", "openstack", "qemu", "secex", "virtualbox", "vmware", "vultr"}
1818

src/cmd-osbuild

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
dn=$(dirname "$0")
5+
# shellcheck source=src/cmdlib.sh
6+
. "${dn}"/cmdlib.sh
7+
8+
9+
print_help() {
10+
cat 1>&2 <<EOF
11+
Usage: coreos-assembler buildextend-${platform} --help
12+
coreos-assembler buildextend-${platform} [--build ID]
13+
14+
Build a bare metal image.
15+
EOF
16+
}
17+
18+
19+
# Parse the passed config JSON and extract a mandatory value
20+
getconfig() {
21+
k=$1
22+
config=$2
23+
jq -re .\""$k"\" < "${config}"
24+
}
25+
# Return a configuration value, or default if not set
26+
getconfig_def() {
27+
k=$1
28+
shift
29+
default=$1
30+
config=$2
31+
jq -re .\""$k"\"//\""${default}"\" < "${config}"
32+
}
33+
34+
# Store information about the artifact into meta.json and also
35+
# "finalize" it, copying it into the builddir.
36+
postprocess_artifact() {
37+
local artifact_name=$1
38+
local local_filepath=$2
39+
local target_filename=$3
40+
local skip_compress=$4
41+
if [[ ! "${skip_compress}" =~ ^(True|False)$ ]]; then
42+
fatal "Must specify 'True' or 'False' for skip_compress. Provided: '${skip_compress}'"
43+
fi
44+
cosa meta --workdir "${workdir}" --build "${build}" --dump | python3 -c "
45+
import sys, json
46+
j = json.load(sys.stdin)
47+
j['images']['${artifact_name}'] = {
48+
'path': '${target_filename}',
49+
'sha256': '$(sha256sum_str < "${local_filepath}")',
50+
'size': $(stat -c '%s' "${local_filepath}")
51+
}
52+
if ${skip_compress}:
53+
j['images']['${artifact_name}']['skip-compression'] = True
54+
json.dump(j, sys.stdout, indent=4)
55+
" > meta.json.new
56+
cosa meta --workdir "${workdir}" --build "${build}" --artifact-json meta.json.new
57+
/usr/lib/coreos-assembler/finalize-artifact "${local_filepath}" "${builddir}/${target_filename}"
58+
echo "Successfully generated: ${target_filename}"
59+
}
60+
61+
# For qemu-secex we need to do a few extra things like spin up a
62+
# VM to run genprotimg and save off the pubkey for Ignition.
63+
postprocess_qemu_secex() {
64+
if [ ! -f "${genprotimgvm}" ]; then
65+
fatal "No genprotimgvm provided at ${genprotimgvm}"
66+
fi
67+
68+
# Basic qemu args:
69+
qemu_args=(); blk_size="512"
70+
[[ $platform == metal4k ]] && blk_size="4096"
71+
qemu_args+=("-drive" "if=none,id=target,format=qcow,file=${imgpath},cache=unsafe" \
72+
"-device" "virtio-blk,serial=target,drive=target,physical_block_size=${blk_size},logical_block_size=${blk_size}")
73+
74+
# SecureVM (holding Universal Key for all IBM Z Mainframes) requires scripts to execute genprotimg
75+
se_script_dir="/usr/lib/coreos-assembler/secex-genprotimgvm-scripts"
76+
genprotimg_img="${PWD}/secex-genprotimg.img"
77+
genprotimg_dir=$(mktemp -p "${tmp_builddir}" -d)
78+
cp "${se_script_dir}/genprotimg-script.sh" "${se_script_dir}/post-script.sh" "${genprotimg_dir}"
79+
# Extra kargs with dm-verity hashes
80+
secex_kargs="ignition.firstboot"
81+
secex_kargs+=" rootfs.roothash=$(<"${outdir}/${platform}/rootfs_hash")"
82+
secex_kargs+=" bootfs.roothash=$(<"${outdir}/${platform}/bootfs_hash")"
83+
echo "${secex_kargs}" > "${genprotimg_dir}/parmfile"
84+
virt-make-fs --format=raw --type=ext4 "${genprotimg_dir}" "${genprotimg_img}"
85+
rm -rf "${genprotimg_dir}"
86+
qemu_args+=("-drive" "if=none,id=genprotimg,format=raw,file=${genprotimg_img}" \
87+
"-device" "virtio-blk,serial=genprotimg,drive=genprotimg")
88+
89+
# GPG keys used for protecting Ignition config
90+
tmp_gpg_home=$(mktemp -p "${tmp_builddir}" -d)
91+
ignition_pubkey=$(mktemp -p "${tmp_builddir}")
92+
ignition_prikey=$(mktemp -p "${tmp_builddir}")
93+
gpg --homedir "${tmp_gpg_home}" --batch --passphrase '' --yes --quick-gen-key "Secure Execution (secex) ${build}" rsa4096 encr none
94+
gpg --homedir "${tmp_gpg_home}" --armor --export secex > "${ignition_pubkey}"
95+
gpg --homedir "${tmp_gpg_home}" --armor --export-secret-key secex > "${ignition_prikey}"
96+
exec 9<"${ignition_prikey}"
97+
rm -rf "${tmp_gpg_home}" "${ignition_prikey}"
98+
qemu_args+=("-add-fd" "fd=9,set=3" "-drive" "if=none,id=gpgkey,format=raw,file=/dev/fdset/3,readonly=on" \
99+
"-device" "virtio-blk,serial=gpgkey,drive=gpgkey")
100+
101+
/usr/lib/coreos-assembler/secex-genprotimgvm-scripts/runvm.sh \
102+
--genprotimgvm "${genprotimgvm}" -- "${qemu_args[@]}"
103+
rm -f "${genprotimg_img}"
104+
exec 9>&-
105+
106+
# Now store the generated ${ignition_pubkey} in the builddir and meta.json
107+
gpg_key_filename="${name}-${build}-ignition-secex-key.gpg.pub"
108+
postprocess_artifact "ignition-gpg-key" "${ignition_pubkey}" "${gpg_key_filename}" 'True'
109+
}
110+
111+
# Here we generate the input JSON we pass to runvm_osbuild for all of our image builds
112+
generate_runvm_osbuild_config() {
113+
runvm_osbuild_config_json="${workdir}/tmp/runvm-osbuild-config-${build}.json"
114+
echo "${runvm_osbuild_config_json}" # Let the caller know where the config will be
115+
if [ -f "${runvm_osbuild_config_json}" ]; then
116+
return # only need to generate this once per build
117+
fi
118+
rm -f "${workdir}"/tmp/runvm-osbuild-config-*.json # clean up any previous configs
119+
120+
# reread these values from the build itself rather than rely on the ones loaded
121+
# by prepare_build since the config might've changed since then
122+
ostree_commit=$(meta_key ostree-commit)
123+
ostree_ref=$(meta_key ref)
124+
if [ "${ostree_ref}" = "None" ]; then
125+
ostree_ref=""
126+
fi
127+
128+
ostree_repo=${tmprepo}
129+
# Ensure that we have the cached unpacked commit
130+
import_ostree_commit_for_build "${build}"
131+
# Note this overwrote the bits generated in prepare_build
132+
# for image_json. In the future we expect to split prepare_build
133+
# into prepare_ostree_build and prepare_diskimage_build; the
134+
# latter path would only run this.
135+
image_json=${workdir}/tmp/image.json
136+
137+
# Grab a few values from $image_json
138+
deploy_via_container=$(getconfig_def "deploy-via-container" "" "${image_json}")
139+
extra_kargs="$(python3 -c 'import sys, json; args = json.load(sys.stdin)["extra-kargs"]; print(" ".join(args))' < "${image_json}")"
140+
141+
# OStree container ociarchive file path
142+
ostree_container="${builddir}/$(meta_key images.ostree.path)"
143+
# If no container_imgref was set let's just set it to some professional
144+
# looking default. The name of the ociarchive file should suffice.
145+
container_imgref_default="ostree-image-signed:oci-archive:/$(basename "${ostree_container}")"
146+
container_imgref=$(getconfig_def "container_imgref" "${container_imgref_default}" "${image_json}")
147+
148+
echo "Estimating disk size..." >&2
149+
# The additional 35% here is obviously a hack, but we can't easily completely fill the filesystem,
150+
# and doing so has apparently negative performance implications.
151+
ostree_size_json="$(/usr/lib/coreos-assembler/estimate-commit-disk-size --repo "$ostree_repo" "$ostree_commit" --add-percent 35)"
152+
rootfs_size_mb="$(jq '."estimate-mb".final' <<< "${ostree_size_json}")"
153+
# The minimum size of a disk image we'll need will be the rootfs_size
154+
# estimate plus the size of the non-root partitions. We'll use this
155+
# size for the metal images, but for the IaaS/virt image we'll use
156+
# the size set in the configs since some of them have minimum sizes that
157+
# the platforms require and we want a "default" disk size that has some
158+
# free space.
159+
nonroot_partition_sizes=513
160+
# On s390x there is one more build - Secure Execution case, which has
161+
# different image layout. We add the sizes of the se and verity
162+
# partitions so that they don't "eat into" the 35% buffer (though note
163+
# this is all blown away on first boot anyway). For 's390x.mpp.yaml'
164+
# simplicity all s390x images have same size (of secex image).
165+
if [[ $basearch == "s390x" ]]; then
166+
nonroot_partition_sizes=$((nonroot_partition_sizes + 200 + 128 + 256 + 1))
167+
fi
168+
metal_image_size_mb="$(( rootfs_size_mb + nonroot_partition_sizes ))"
169+
cloud_image_size_mb="$(jq -r ".size*1024" < "${image_json}")"
170+
echo "Disk sizes: metal: ${metal_image_size_mb}M (estimated), cloud: ${cloud_image_size_mb}M" >&2
171+
172+
# Generate the JSON describing the disk we want to build
173+
yaml2json /dev/stdin "${runvm_osbuild_config_json}" <<EOF
174+
container-imgref: "${container_imgref}"
175+
deploy-via-container: "${deploy_via_container}"
176+
osname: "${name}"
177+
ostree-container: "${ostree_container}"
178+
ostree-ref: "${ref}"
179+
extra-kargs-string: "${extra_kargs}"
180+
ostree-repo: "${ostree_repo}"
181+
metal-image-size: "${metal_image_size_mb}"
182+
cloud-image-size: "${cloud_image_size_mb}"
183+
# Note: this is only used in the secex case; there, the rootfs is
184+
# not the last partition on the disk so we need to explicitly size it
185+
rootfs-size: "${rootfs_size_mb}"
186+
EOF
187+
}
188+
189+
190+
main() {
191+
# Set Some Defaults
192+
genprotimgvm=/data.secex/genprotimgvm.qcow2
193+
build=
194+
force=
195+
196+
# This script is used for creating several artifacts. For example,
197+
# `cmd-buildextend-qemu` is a symlink to `cmd-buildextend-metal`.
198+
case "$(basename "$0")" in
199+
"cmd-buildextend-metal") platform=metal;;
200+
"cmd-buildextend-metal4k") platform=metal4k;;
201+
"cmd-buildextend-qemu") platform=qemu;;
202+
"cmd-buildextend-qemu-secex") platform=qemu-secex;;
203+
"cmd-buildextend-secex") platform=qemu-secex;;
204+
*) fatal "called as unexpected name $0";;
205+
esac
206+
207+
options=$(getopt --options h --longoptions help,force,build:,genprotimgvm: -- "$@") || {
208+
print_help
209+
exit 1
210+
}
211+
eval set -- "$options"
212+
while true; do
213+
case "$1" in
214+
-h | --help)
215+
print_help
216+
exit 0
217+
;;
218+
--force)
219+
force=1
220+
;;
221+
--build)
222+
build=$2
223+
shift
224+
;;
225+
--genprotimgvm)
226+
genprotimgvm="$2"
227+
shift
228+
;;
229+
--)
230+
shift
231+
break
232+
;;
233+
-*)
234+
fatal "$0: unrecognized option: $1"
235+
;;
236+
*)
237+
break
238+
;;
239+
esac
240+
shift
241+
done
242+
243+
if [ $# -ne 0 ]; then
244+
print_help
245+
fatal "Too many arguments passed"
246+
fi
247+
248+
case "$basearch" in
249+
"x86_64"|"aarch64"|"s390x"|"ppc64le") ;;
250+
*) fatal "$basearch is not supported for this command" ;;
251+
esac
252+
253+
# shellcheck disable=SC2031
254+
export LIBGUESTFS_BACKEND=direct
255+
export IMAGE_TYPE="${platform}"
256+
prepare_build
257+
258+
if [ -z "${build}" ]; then
259+
build=$(get_latest_build)
260+
if [ -z "${build}" ]; then
261+
fatal "No build found."
262+
fi
263+
fi
264+
265+
builddir=$(get_build_dir "$build")
266+
if [ ! -d "${builddir}" ]; then
267+
fatal "Build dir ${builddir} does not exist."
268+
fi
269+
270+
# add building sempahore
271+
build_semaphore="${builddir}/.${platform}.building"
272+
if [ -e "${build_semaphore}" ]; then
273+
fatal "${build_semaphore} found: another process is building ${platform}"
274+
fi
275+
touch "${build_semaphore}"
276+
trap 'rm -f ${build_semaphore}' EXIT
277+
278+
# check if the image already exists in the meta.json
279+
if [ -z "${force}" ]; then
280+
meta_img=$(meta_key "images.${platform}.path")
281+
if [ "${meta_img}" != "None" ]; then
282+
echo "${platform} image already exists:"
283+
echo "$meta_img"
284+
exit 0
285+
fi
286+
fi
287+
288+
# reread these values from the build itself rather than rely on the ones loaded
289+
# by prepare_build since the config might've changed since then
290+
name=$(meta_key name)
291+
292+
image_format=raw
293+
if [[ "${platform}" == "qemu" || "${platform}" == "qemu-secex" ]]; then
294+
image_format=qcow2
295+
fi
296+
297+
imgname=${name}-${build}-${platform}.${basearch}.${image_format}
298+
imgpath=${PWD}/${imgname}
299+
300+
# In the jenkins pipelines we build the qemu image first and that operation
301+
# will do a lot of the same work required for later artifacts (metal, metal4k, etc)
302+
# so we want the cached output from that run to persist. The later artifacts get
303+
# built in parallel, so we need to be able to access the cache by multiple processes,
304+
# so for those we'll set `snapshot=on` so that each will get their own disk image.
305+
# This is OK because we don't checkpoint (cache) any of those stages.
306+
[ "${platform}" == "qemu" ] && snapshot="off" || snapshot="on"
307+
runvm_osbuild_config_json="$(generate_runvm_osbuild_config)"
308+
outdir=$(mktemp -p "${tmp_builddir}" -d)
309+
runvm_with_cache_snapshot "$snapshot" -- /usr/lib/coreos-assembler/runvm-osbuild \
310+
--config "${runvm_osbuild_config_json}" \
311+
--mpp "/usr/lib/coreos-assembler/osbuild-manifests/coreos.osbuild.${basearch}.mpp.yaml" \
312+
--outdir "${outdir}" \
313+
--platform "${platform}"
314+
315+
mv "${outdir}/${platform}/${platform}" "${imgpath}"
316+
317+
case "$platform" in
318+
qemu-secex)
319+
# Massage the generated artifact through an extra VM for secex. This
320+
# will also create an Ignition pubkey and store it in the meta.json
321+
# and builddir.
322+
postprocess_qemu_secex
323+
# Also need to update the meta.json and builddir with the main artifact.
324+
postprocess_artifact "${platform}" "${imgpath}" "${imgname}" 'False'
325+
;;
326+
*)
327+
# Update the meta.json and builddir with the generated artifact.
328+
postprocess_artifact "${platform}" "${imgpath}" "${imgname}" 'False'
329+
;;
330+
esac
331+
332+
# clean up the tmpbuild
333+
rm -rf "${tmp_builddir}"
334+
}
335+
336+
main "$@"

0 commit comments

Comments
 (0)