Skip to content

Commit 0312305

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 ea7beb7 commit 0312305

File tree

2 files changed

+336
-1
lines changed

2 files changed

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

0 commit comments

Comments
 (0)