debian-forge-composer/test/cases/api/gcp.sh
Tomáš Hozza d7e59e6eec Worker: move GCE image guest OS features to upload target options
Previously, the worker was determining the GCE image guest OS Features
on its own, based on the OS name. This caused problems, in case the
osbuild-composer was of a newer version than the worker.

Example:
osbuild-composer contained support for c10s GCE image type and its
implementation also contained the proper guest OS Features list for it.
However, when the worker got the osbuild job, it built it and tried to
fetch the guest OS Features for the distro. Since its implementation was
too old, it didn't contain the code that added the actual support for
c10s GCE images and got no guest OS features list (which is the default
for unsupported distros). The image was successfully uploaded and
shared, but it does not boot in GCP, because it does not know that it
should use UEFI to boot it.

This behavior could be considered a bug. The worker should be dumb. It
should not be making decisions about the image features, but instead it
should take them from the upload target options. And composer should be
the authoritative source of truth for this. Because otherwise, we
basically have two components that need to be updated in sync to add
support for GCE images on a new distro.

Move the GCE image guest OS features to the GCP upload target options.
The worker will just take what is specified there and use it when
importing the image to GCP. As a compatibility layer for the case when
the composer would be older than the worker (unlikely, but still),
worker will try to determine the image guest OS features in case the
list in the upload target options is empty.

Extend the GCP functional tests to check that the imported image has at
least some guest OS features set.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
2024-08-29 17:37:48 +02:00

195 lines
7.3 KiB
Bash

#!/usr/bin/bash
source /usr/libexec/tests/osbuild-composer/api/common/common.sh
# Check that needed variables are set to access GCP.
function checkEnv() {
printenv GOOGLE_APPLICATION_CREDENTIALS GCP_BUCKET GCP_REGION GCP_API_TEST_SHARE_ACCOUNT > /dev/null
}
function cleanup() {
# since this function can be called at any time, ensure that we don't expand unbound variables
GCP_CMD="${GCP_CMD:-}"
GCP_IMAGE_NAME="${GCP_IMAGE_NAME:-}"
GCP_INSTANCE_NAME="${GCP_INSTANCE_NAME:-}"
GCP_ZONE="${GCP_ZONE:-}"
if [ -n "$GCP_CMD" ]; then
$GCP_CMD compute instances delete --zone="$GCP_ZONE" "$GCP_INSTANCE_NAME"
$GCP_CMD compute images delete "$GCP_IMAGE_NAME"
fi
}
function installClient() {
if ! hash gcloud; then
echo "Using 'gcloud' from a container"
sudo "${CONTAINER_RUNTIME}" pull "${CONTAINER_IMAGE_CLOUD_TOOLS}"
# directory mounted to the container, in which gcloud stores the credentials after logging in
GCP_CMD_CREDS_DIR="${WORKDIR}/gcloud_credentials"
mkdir "${GCP_CMD_CREDS_DIR}"
GCP_CMD="sudo ${CONTAINER_RUNTIME} run --rm \
-v ${GCP_CMD_CREDS_DIR}:/root/.config/gcloud:Z \
-v ${GOOGLE_APPLICATION_CREDENTIALS}:${GOOGLE_APPLICATION_CREDENTIALS}:Z \
-v ${WORKDIR}:${WORKDIR}:Z \
${CONTAINER_IMAGE_CLOUD_TOOLS} gcloud --quiet"
else
echo "Using pre-installed 'gcloud' from the system"
GCP_CMD="gcloud --quiet"
fi
$GCP_CMD --version
}
function createReqFile() {
# constrains for GCP resource IDs:
# - max 62 characters
# - must be a match of regex '[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?|[1-9][0-9]{0,19}'
#
# use sha224sum to get predictable 56 characters long testID without invalid characters
GCP_TEST_ID_HASH="$(echo -n "$TEST_ID" | sha224sum - | sed -E 's/([a-z0-9])\s+-/\1/')"
GCP_IMAGE_NAME="image-$GCP_TEST_ID_HASH"
cat > "$REQUEST_FILE" << EOF
{
"distribution": "$DISTRO",
"customizations": {
"filesystem": [
{
"mountpoint": "/var",
"min_size": 262144000
}
],
"payload_repositories": [
{
"baseurl": "$PAYLOAD_REPO_URL"
}
],
"packages": [
"postgresql",
"dummy"
]${SUBSCRIPTION_BLOCK}${DIR_FILES_CUSTOMIZATION_BLOCK}${REPOSITORY_CUSTOMIZATION_BLOCK}${OPENSCAP_CUSTOMIZATION_BLOCK}
${TIMEZONE_CUSTOMIZATION_BLOCK}${FIREWALL_CUSTOMIZATION_BLOCK}${RPM_CUSTOMIZATION_BLOCK}${RHSM_CUSTOMIZATION_BLOCK}
},
"image_request": {
"architecture": "$ARCH",
"image_type": "${IMAGE_TYPE}",
"repositories": $(jq ".\"$ARCH\"" /usr/share/tests/osbuild-composer/repositories/"$DISTRO".json),
"upload_options": {
"region": "${GCP_REGION}",
"image_name": "${GCP_IMAGE_NAME}",
"share_with_accounts": ["${GCP_API_TEST_SHARE_ACCOUNT}"]
}
}
}
EOF
}
function checkUploadStatusOptions() {
GCP_PROJECT=$(jq -r '.project_id' "$GOOGLE_APPLICATION_CREDENTIALS")
local IMAGE_NAME
IMAGE_NAME=$(echo "$UPLOAD_OPTIONS" | jq -r '.image_name')
local PROJECT_ID
PROJECT_ID=$(echo "$UPLOAD_OPTIONS" | jq -r '.project_id')
test "$IMAGE_NAME" = "$GCP_IMAGE_NAME"
test "$PROJECT_ID" = "$GCP_PROJECT"
}
# Log into GCP
function cloud_login() {
# Authenticate
$GCP_CMD auth activate-service-account --key-file "$GOOGLE_APPLICATION_CREDENTIALS"
# Extract and set the default project to be used for commands
GCP_PROJECT=$(jq -r '.project_id' "$GOOGLE_APPLICATION_CREDENTIALS")
$GCP_CMD config set project "$GCP_PROJECT"
}
# Verify image in Compute Engine on GCP
function verify() {
cloud_login
# Add "gitlab-ci-test" label to the image
$GCP_CMD compute images add-labels "$GCP_IMAGE_NAME" --labels=gitlab-ci-test=true
# Verify that the image was shared
SHARE_OK=1
$GCP_CMD --format=json compute images get-iam-policy "$GCP_IMAGE_NAME" > "$WORKDIR/image-iam-policy.json"
SHARED_ACCOUNT=$(jq -r '.bindings[0].members[0]' "$WORKDIR/image-iam-policy.json")
SHARED_ROLE=$(jq -r '.bindings[0].role' "$WORKDIR/image-iam-policy.json")
if [ "$SHARED_ACCOUNT" != "$GCP_API_TEST_SHARE_ACCOUNT" ] || [ "$SHARED_ROLE" != "roles/compute.imageUser" ]; then
SHARE_OK=0
fi
if [ "$SHARE_OK" != 1 ]; then
echo "GCP image wasn't shared with the GCP_API_TEST_SHARE_ACCOUNT. 😢"
exit 1
fi
# Verify that the image has guestOsFeatures set
GCP_IMAGE_GUEST_OS_FEATURES_LEN=$($GCP_CMD compute images describe --project="$GCP_PROJECT" --format="json" "$GCP_IMAGE_NAME" | jq -r '.guestOsFeatures | length')
if [ "$GCP_IMAGE_GUEST_OS_FEATURES_LEN" -eq 0 ]; then
echo "❌ Image does not have guestOsFeatures set"
exit 1
fi
# Verify that the image boots and have customizations applied
# Create SSH keys to use
GCP_SSH_KEY="$WORKDIR/id_google_compute_engine"
ssh-keygen -t rsa-sha2-512 -f "$GCP_SSH_KEY" -C "$SSH_USER" -N ""
# TODO: remove this once el10 / c10s image moves to oslogin
GCP_METADATA_OPTION=
# On el10 / c10s, we need to temporarily set the metadata key to "ssh-keys", because there is no "oslogin" feature
if [[ ($ID == rhel || $ID == centos) && ${VERSION_ID%.*} == 10 ]]; then
GCP_SSH_METADATA_FILE="$WORKDIR/gcp-ssh-keys-metadata"
echo "${SSH_USER}:$(cat "$GCP_SSH_KEY".pub)" > "$GCP_SSH_METADATA_FILE"
GCP_METADATA_OPTION="--metadata-from-file=ssh-keys=$GCP_SSH_METADATA_FILE"
fi
# create the instance
# resource ID can have max 62 characters, the $GCP_TEST_ID_HASH contains 56 characters
GCP_INSTANCE_NAME="vm-$GCP_TEST_ID_HASH"
# Ensure that we use random GCP region with available 'IN_USE_ADDRESSES' quota
# We use the CI variable "GCP_REGION" as the base for expression to filter regions.
# It works best if the "GCP_REGION" is set to a storage multi-region, such as "us"
local GCP_COMPUTE_REGION
GCP_COMPUTE_REGION=$($GCP_CMD --format=json compute regions list --filter="name:$GCP_REGION* AND status=UP" | jq -r '.[] | select(.quotas[] as $quota | $quota.metric == "IN_USE_ADDRESSES" and $quota.limit > $quota.usage) | .name' | shuf -n1)
# Randomize the used GCP zone to prevent hitting "exhausted resources" error on each test re-run
GCP_ZONE=$($GCP_CMD --format=json compute zones list --filter="region=$GCP_COMPUTE_REGION AND status=UP" | jq -r '.[].name' | shuf -n1)
# Pick the smallest '^n\d-standard-\d$' machine type from those available in the zone
local GCP_MACHINE_TYPE
GCP_MACHINE_TYPE=$($GCP_CMD --format=json compute machine-types list --filter="zone=$GCP_ZONE AND name~^n\d-standard-\d$" | jq -r '.[].name' | sort | head -1)
# shellcheck disable=SC2086
$GCP_CMD compute instances create "$GCP_INSTANCE_NAME" \
--zone="$GCP_ZONE" \
--image-project="$GCP_PROJECT" \
--image="$GCP_IMAGE_NAME" \
--machine-type="$GCP_MACHINE_TYPE" \
$GCP_METADATA_OPTION --labels=gitlab-ci-test=true
HOST=$($GCP_CMD --format=json compute instances describe "$GCP_INSTANCE_NAME" --zone="$GCP_ZONE" --format='get(networkInterfaces[0].accessConfigs[0].natIP)')
echo "⏱ Waiting for GCP instance to respond to ssh"
_instanceWaitSSH "$HOST"
# Verify image
_ssh="$GCP_CMD compute ssh --strict-host-key-checking=no --ssh-key-file=$GCP_SSH_KEY --zone=$GCP_ZONE $SSH_USER@$GCP_INSTANCE_NAME --"
# TODO: remove this once el10 / c10s image moves to oslogin
# On el10 / c10s, we need to ssh directly, because there is no "oslogin" feature
if [[ ($ID == rhel || $ID == centos) && ${VERSION_ID%.*} == 10 ]]; then
_ssh="ssh -oStrictHostKeyChecking=no -i $GCP_SSH_KEY $SSH_USER@$HOST"
fi
_instanceCheck "$_ssh"
}