Skip to content

Commit

Permalink
podvm: Support for pre-built podvm image for Azure
Browse files Browse the repository at this point in the history
Allows creating image version from pre-built qcow2/raw/vhd image.
The pre-built image is available in a container image and
the details needs to be specified in PODVM_IMAGE_URI
in azure-podvm-image-cm.yaml.

Azure accepts 1MB aligned VHD image only,
so a qcow2 or raw will be converted to the same before uploading.

qemu-img is used for the conversion.

eg:
oci::quay.io/openshift_sandboxed_containers/azure-podvm-image:latest::/image/podvm.vhd
oci::quay.io/openshift_sandboxed_containers/azure-podvm-image:latest::/podvm.qcow2

Signed-off-by: Pradipta Banerjee <pradipta.banerjee@gmail.com>

podvm-builder: Add qemu-img package

qemu-img will be used to verify the pre-built image as well
as convert it to different format as needed

Signed-off-by: Pradipta Banerjee <pradipta.banerjee@gmail.com>
  • Loading branch information
bpradipt committed Sep 18, 2024
1 parent 1659c9a commit f96c565
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 21 deletions.
16 changes: 15 additions & 1 deletion config/peerpods/podvm/Dockerfile.podvm-builder
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ FROM registry.access.redhat.com/ubi9/ubi:9.4


LABEL kata_src=https://github.com/kata-containers/kata-containers
LABEL kata_src_commit=stable-3.6
LABEL kata_src_commit=stable-3.7

ARG ORG_ID
ARG ACTIVATION_KEY

# This registering RHEL when building on an unsubscribed system
# If you are running a UBI container on a registered and subscribed RHEL host,
# the main RHEL Server repository is enabled inside the standard UBI container
RUN if [[ -n "${ACTIVATION_KEY}" && -n "${ORG_ID}" ]]; then \
rm -f /etc/rhsm-host && rm -f /etc/pki/entitlement-host; \
subscription-manager register --org=${ORG_ID} --activationkey=${ACTIVATION_KEY}; \
fi

RUN mkdir -p /scripts

Expand All @@ -19,6 +30,8 @@ RUN /scripts/azure-podvm-image-handler.sh -- install_rpms
ARG CAA_SRC=https://github.com/confidential-containers/cloud-api-adaptor
ARG CAA_REF=main
ARG CERT_RPM


ENV CAA_SRC=$CAA_SRC
ENV CAA_REF=$CAA_REF
ENV CERT_RPM=$CERT_RPM
Expand All @@ -27,6 +40,7 @@ RUN if [[ -n "$CERT_RPM" ]] ; then \
dnf install -y $CERT_RPM ; \
fi


RUN git clone ${CAA_SRC} -b ${CAA_REF} /src/cloud-api-adaptor

ADD podvm-builder.sh /podvm-builder.sh
Expand Down
173 changes: 158 additions & 15 deletions config/peerpods/podvm/azure-podvm-image-handler.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@ function add_azure_repositories() {
echo "Azure yum repositories added successfully"
}

function set_image_version_and_name() {
# Set the image version
# It should follow the Major(int).Minor(int).Patch(int)
IMAGE_VERSION="${IMAGE_VERSION_MAJ_MIN}.$(date +'%Y%m%d%S')"
export IMAGE_VERSION

# Set the image name
IMAGE_NAME="${IMAGE_BASE_NAME}-${IMAGE_VERSION}"
export IMAGE_NAME
}

# function to install azure CLI

function install_azure_cli() {
Expand Down Expand Up @@ -234,15 +245,8 @@ function create_image_using_packer() {
# If any error occurs, exit the script with an error message
# The variables are set before calling the function

# Set the image version
# It should follow the Major(int).Minor(int).Patch(int)
IMAGE_VERSION="${IMAGE_VERSION_MAJ_MIN}.$(date +'%Y%m%d%S')"
export IMAGE_VERSION

# Set the image name
IMAGE_NAME="${IMAGE_BASE_NAME}-${IMAGE_VERSION}"
export IMAGE_NAME

# Set the image version and name
set_image_version_and_name
# Set the base image details

if [[ "${PODVM_DISTRO}" == "rhel" ]]; then
Expand Down Expand Up @@ -490,6 +494,28 @@ function create_image() {
install_binary_packages
fi

# Based on the value of `IMAGE_TYPE` the image is either build from scratch or using the prebuilt artifact.
if [[ "${IMAGE_TYPE}" == "operator-built" ]]; then
create_azure_image_from_scratch
elif [[ "${IMAGE_TYPE}" == "pre-built" ]]; then
create_azure_image_from_prebuilt_artifact
fi

# Get the image id of the newly created image.
# This will set the IMAGE_ID variable
get_image_id

# Add the image id as annotation to peer-pods-cm configmap
add_image_id_annotation_to_peer_pods_cm

echo "Azure image created successfully"

}

# Function to create the azure image from scratch using packer
function create_azure_image_from_scratch() {
echo "Creating Azure image from scratch"

if [[ "${DOWNLOAD_SOURCES}" == "yes" ]]; then
# Download source code from GitHub
download_source_code
Expand All @@ -504,15 +530,132 @@ function create_image() {
# Create Azure image using packer
create_image_using_packer

# Get the image id of the newly created image.
# This will set the IMAGE_ID variable
get_image_id
echo "Azure image created successfully from scratch"
}

# Add the image id as annotation to peer-pods-cm configmap
add_image_id_annotation_to_peer_pods_cm
# Function to create the azure image from prebuilt artifact
# The prebuilt artifact is expected to be a vhd image

echo "Azure image created successfully"
function create_azure_image_from_prebuilt_artifact() {
echo "Creating Azure image from prebuilt artifact"

# Set the IMAGE_VERSION and IMAGE_NAME
set_image_version_and_name

echo "Pulling the podvm image from the provided path"
image_src="/tmp/image"
extraction_destination_path="/image"
image_repo_auth_file="/tmp/regauth/auth.json"

# Get the PODVM_IMAGE_TYPE, PODVM_IMAGE_TAG and PODVM_IMAGE_SRC_PATH
get_image_type_url_and_path

case "${PODVM_IMAGE_TYPE}" in
oci)
echo "Extracting the Azure image from the given path."

mkdir -p "${extraction_destination_path}" ||
error_exit "Failed to create the image directory"

extract_container_image "${PODVM_IMAGE_URL}" \
"${PODVM_IMAGE_TAG}" \
"${image_src}" \
"${extraction_destination_path}" \
"${image_repo_auth_file}"

# Form the path of the podvm vhd image.
podvm_image_path="${extraction_destination_path}/rootfs/${PODVM_IMAGE_SRC_PATH}"

# Convert the podvm image to vhd if it's not a vhd image
vhd_image_path=$(convert_podvm_image_to_vhd "${podvm_image_path}")

# Upload the vhd to the storage container
# This will set the VHD_URL global variable
upload_vhd_image "${vhd_image_path}" "${IMAGE_NAME}"

# Create the image version from the VHD
az sig image-version create \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--gallery-name "${IMAGE_GALLERY_NAME}" \
--gallery-image-definition "${IMAGE_DEFINITION_NAME}" \
--gallery-image-version "${IMAGE_VERSION}" \
--os-vhd-uri "${VHD_URL}" \
--os-vhd-storage-account "${STORAGE_ACCOUNT_NAME}" \
--target-regions "${AZURE_REGION}" ||
error_exit "Failed to create the image version"

# Clean up
rm "${podvm_image_path}"
az storage account delete \
--name "${STORAGE_ACCOUNT_NAME}" \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--yes ||
error_exit "Failed to delete the storage account"

;;
*)
error_exit "Currently only OCI image unpacking is supported, exiting."
;;
esac

echo "Azure image created successfully from prebuilt artifact"
}

# Function to upload the vhd to the volume

function upload_vhd_image() {
echo "Uploading the vhd to the storage container"

local vhd_path="${1}"
local image_name="${2}"

[[ -z "${vhd_path}" ]] && error_exit "VHD path is empty"

# Create a storage account if it doesn't exist
STORAGE_ACCOUNT_NAME="podvmartifacts$(date +%s)"
az storage account create \
--name "${STORAGE_ACCOUNT_NAME}" \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--location "${AZURE_REGION}" \
--sku Standard_LRS \
--encryption-services blob ||
error_exit "Failed to create the storage account"

# Get storage account key
STORAGE_ACCOUNT_KEY=$(az storage account keys list \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--account-name "${STORAGE_ACCOUNT_NAME}" \
--query '[0].value' \
-o tsv) ||
error_exit "Failed to get the storage account key"

# Create a container in the storage account
CONTAINER_NAME="podvm-artifacts"
az storage container create \
--name "${CONTAINER_NAME}" \
--account-name "${STORAGE_ACCOUNT_NAME}" \
--account-key "${STORAGE_ACCOUNT_KEY}" ||
error_exit "Failed to create the storage container"

# Upload the VHD to the storage container
az storage blob upload --account-name "${STORAGE_ACCOUNT_NAME}" \
--account-key "${STORAGE_ACCOUNT_KEY}" \
--container-name "${CONTAINER_NAME}" \
--file "${vhd_path}" \
--name "${image_name}" ||
error_exit "Failed to upload the VHD to the storage container"

# Get the URL of the uploaded VHD
VHD_URL=$(az storage blob url \
--account-name "${STORAGE_ACCOUNT_NAME}" \
--account-key "${STORAGE_ACCOUNT_KEY}" \
--container-name "${CONTAINER_NAME}" \
--name "${image_name}" -o tsv) ||
error_exit "Failed to get the URL of the uploaded VHD"

export VHD_URL

echo "VHD uploaded successfully"
}

# Function to delete a specific image version from Azure
Expand Down
126 changes: 121 additions & 5 deletions config/peerpods/podvm/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function install_rpm_packages() {
"unzip"
"skopeo"
"jq"
"qemu-img" # for handling pre-built images. Note that this rpm requires subscription
)

# Create a new array to store rpm packages that are not installed
Expand Down Expand Up @@ -309,6 +310,10 @@ function extract_container_image() {
umoci unpack --rootless --image "${dest_image}:${image_tag}" "${destination_path}" ||
error_exit "Failed to extract the container image"

# Display the content of the destination_path
echo "Extracted container image content:"
ls -l "${destination_path}"

}

# These are cloud-init modules we allow for the CoCo case, it's mostly used to disable ssh
Expand Down Expand Up @@ -395,16 +400,127 @@ function get_image_type_url_and_path() {
export PODVM_IMAGE_TYPE PODVM_IMAGE_URL PODVM_IMAGE_TAG PODVM_IMAGE_SRC_PATH
}

# Function to get format of the podvm image
# Input: podvm image path
# Use qemu-img info to get the image info
function get_podvm_image_format() {
image_path="${1}"
echo "Getting type of the PodVM image: ${image_path}"

image_format=$(qemu-img info -f raw --output json "${image_path}" | jq '.format') ||
error_exit "Failed to get podvm image info"
echo "PodVM image type: ${image_format}"
echo "${image_format}"
}

# Function to validate the podvm image type.
# Input: podvm image path
function validate_podvm_image() {
PODVM_IMAGE_PATH="${1}"
image_path="${1}"

echo "Validating PodVM image: ${image_path}"

# Get the podvm image format.
image_format=$(get_podvm_image_format "${image_path}")

# Currently only qcow2 based PodVM images are supported for image upload.
if [[ "$(file -b "$PODVM_IMAGE_PATH")" != *QCOW2* ]]; then
error_exit "PodVM image is not a valid qcow2, exiting."
# Check if the format is qcow2, raw or vhd
if [[ "${image_format}" != "qcow2" &&
"${image_format}" != "raw" &&
"${image_format}" != "vhd" ]]; then
error_exit "PodVM image is neither a valid qcow2, raw or vhd, exiting."
fi

echo "Checksum of the PodVM image: $(sha256sum "$PODVM_IMAGE_PATH")"
echo "Checksum of the PodVM image: $(sha256sum "$image_path")"
}

# Function to convert qcow2 image to vhd image
# Input: qcow2 image
# Output: vhddisk image
function convert_qcow2_to_vhd() {
qcow2disk=${1}
rawdisk="$(basename -s qcow2 "${1}")raw"
vhddisk="$(basename -s qcow2 "${1}")vhd"
echo "Qcow2 disk name: ${qcow2disk}"
echo "Raw disk name: ${rawdisk}"
echo "VHD disk name: ${vhddisk}"

# Convert qcow2 to raw
qemu-img convert -f qcow2 -O raw "${qcow2disk}" "${rawdisk}" ||
error_exit "Failed to convert qcow2 to raw"

# Convert raw to vhd
resize_and_convert_raw_to_vhd_image "${rawdisk}"

# Clean up the raw disk
rm -f "${rawdisk}"

echo "Successfully converted qcow2 to vhd image name: ${vhddisk}"
echo "${vhddisk}"
}

# Function to resize and convert raw image to 1MB aligned vhd image for Azure
# Input: raw disk image
# Output: vhddisk image
function resize_and_convert_raw_to_vhd_image() {
rawdisk=${1}
vhddisk="$(basename -s raw "${1}")vhd"

echo "Raw disk name: ${rawdisk}"
echo "VHD disk name: ${vhddisk}"

MB=$((1024 * 1024))
size=$(qemu-img info -f raw --output json "$rawdisk" | jq '."virtual-size"') ||
error_exit "Failed to get raw disk size"

echo "Raw disk size: ${size}"

rounded_size=$(((size + MB - 1) / MB * MB))

echo "Rounded Size = ${rounded_size}"

echo "Rounding up raw disk to 1MB"
qemu-img resize -f raw "$rawdisk" "$rounded_size" ||
error_exit "Failed to resize raw disk"

echo "Converting raw to vhd"
qemu-img convert -f raw -o subformat=fixed,force_size -O vpc "$rawdisk" "$vhddisk" ||
error_exit "Failed to convert raw to vhd"

echo "Successfully converted raw to vhd image name: ${vhddisk}"
echo "${vhddisk}"
}

# Function to check image and convert to vhd if needed
# Input: image
# Output: vhddisk image
function convert_podvm_image_to_vhd() {
image_path=${1}
vhddisk=""

# Get the podvm image type
image_format=$(get_podvm_image_format "${image_path}")

case "${image_format}" in
qcow2)
# Convert the qcow2 image to vhd
vhddisk=$(convert_qcow2_to_vhd "${image_path}")
;;
raw)
# Convert the raw image to vhd
vhddisk=$(resize_and_convert_raw_to_vhd_image "${image_path}")
;;
vhd)
echo "PodVM image is already a vhd image"
vhddisk="${image_path}"
;;

*)
error_exit "Invalid podvm image format: ${image_format}"
;;
esac

echo "Successfully converted podvm image to vhd image name: ${vhddisk}"
echo "${vhddisk}"
}

# Global variables
Expand Down

0 comments on commit f96c565

Please sign in to comment.