debian-forge-composer/test/cases/api.sh
Tomáš Hozza 2fe5e01d4d Test/api.sh: test distro aliases and names without dot-notation
Extend the `api.sh` to allow testing compose requests with distro name
which does not use dot-notation (specifically when the dot is removed
from the distro name as it used to be in the past). In addition to that,
allow also testing the distro alias using distro name without the minor
version in compose requests.

Enable these two new test variants in the CI.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
2024-01-26 11:32:34 +01:00

898 lines
28 KiB
Bash
Executable file

#!/usr/bin/bash
#
# Test osbuild-composer's main API endpoint by building a sample image and
# uploading it to the appropriate cloud provider. The test currently supports
# AWS and GCP.
#
#
# Cloud provider / target names
#
CLOUD_PROVIDER_AWS="aws"
CLOUD_PROVIDER_GCP="gcp"
CLOUD_PROVIDER_AZURE="azure"
CLOUD_PROVIDER_AWS_S3="aws.s3"
CLOUD_PROVIDER_GENERIC_S3="generic.s3"
CLOUD_PROVIDER_CONTAINER_IMAGE_REGISTRY="container"
CLOUD_PROVIDER_OCI="oci"
#
# Supported Image type names
#
export IMAGE_TYPE_AWS="aws"
export IMAGE_TYPE_AWS_SAP_RHUI="aws-sap-rhui"
export IMAGE_TYPE_AZURE="azure"
export IMAGE_TYPE_AZURE_SAP_RHUI="azure-sap-rhui"
export IMAGE_TYPE_EDGE_COMMIT="edge-commit"
export IMAGE_TYPE_EDGE_CONTAINER="edge-container"
export IMAGE_TYPE_EDGE_INSTALLER="edge-installer"
export IMAGE_TYPE_GCP="gcp"
export IMAGE_TYPE_IMAGE_INSTALLER="image-installer"
export IMAGE_TYPE_GUEST="guest-image"
export IMAGE_TYPE_OCI="oci"
export IMAGE_TYPE_VSPHERE="vsphere"
export IMAGE_TYPE_IOT_COMMIT="iot-commit"
if (( $# > 2 )); then
echo "$0 does not support more than two arguments"
exit 1
fi
if (( $# == 0 )); then
echo "$0 requires that you set the image type to build"
exit 1
fi
set -euo pipefail
IMAGE_TYPE="$1"
# set TEST_MODULE_HOTFIXES to 1 to enable module hotfixes for the test
TEST_MODULE_HOTFIXES="${TEST_MODULE_HOTFIXES:-0}"
# set TEST_NO_DOT_NOTATION to 1 to disable dot notation in the test
# Not using the dot notation means that the dot separating the major and minor
# version in the distro name will be removed.
#
# This test variant is mutually exclusive with TEST_DISTRO_ALIAS.
TEST_NO_DOT_NOTATION="${TEST_NO_DOT_NOTATION:-0}"
# set TEST_DISTRO_ALIAS to 1 to test configuring and using a distro alias
# when submitting composer request.
#
# This test variant deliberately uses the SAP image to test that the alias
# works as expected. The reason is that the SAP image will always have DNF
# configuration pinned down to a specific OS version and the version is the
# same as the distro definition used to generate the manifest.
# Specifically, the `/etc/dnf/vars/releasever` file will contain `X.Y`.
#
# This test variant is mutually exclusive with TEST_NO_DOT_NOTATION.
export TEST_DISTRO_ALIAS="${TEST_DISTRO_ALIAS:-0}"
# TEST_NO_DOT_NOTATION and TEST_DISTRO_ALIAS are mutually exclusive
if [[ "$TEST_NO_DOT_NOTATION" == "1" && "$TEST_DISTRO_ALIAS" == "1" ]]; then
echo "TEST_NO_DOT_NOTATION and TEST_DISTRO_ALIAS are mutually exclusive"
exit 1
fi
# TEST_DISTRO_ALIAS requires that the IMAGE_TYPE is a SAP image
if [[ "$TEST_DISTRO_ALIAS" == "1" ]]; then
case "${IMAGE_TYPE}" in
"$IMAGE_TYPE_AWS_SAP_RHUI"|"IMAGE_TYPE_AZURE_SAP_RHUI")
;;
*)
echo "TEST_DISTRO_ALIAS can only be used with SAP images"
exit 1
esac
fi
# select cloud provider based on image type
#
# the supported image types are listed in the api spec (internal/cloudapi/v2/openapi.v2.yml)
case ${IMAGE_TYPE} in
"$IMAGE_TYPE_AWS"|"$IMAGE_TYPE_AWS_SAP_RHUI")
CLOUD_PROVIDER="${CLOUD_PROVIDER_AWS}"
;;
"$IMAGE_TYPE_AZURE"|"IMAGE_TYPE_AZURE_SAP_RHUI")
CLOUD_PROVIDER="${CLOUD_PROVIDER_AZURE}"
;;
"$IMAGE_TYPE_GCP")
CLOUD_PROVIDER="${CLOUD_PROVIDER_GCP}"
;;
"$IMAGE_TYPE_EDGE_CONTAINER")
CLOUD_PROVIDER="${CLOUD_PROVIDER_CONTAINER_IMAGE_REGISTRY}"
;;
"$IMAGE_TYPE_OCI")
CLOUD_PROVIDER="${CLOUD_PROVIDER_OCI}"
;;
"$IMAGE_TYPE_EDGE_COMMIT"|"$IMAGE_TYPE_IOT_COMMIT"|"$IMAGE_TYPE_EDGE_INSTALLER"|"$IMAGE_TYPE_IMAGE_INSTALLER"|"$IMAGE_TYPE_GUEST"|"$IMAGE_TYPE_VSPHERE")
# blobby image types: upload to s3 and provide download link
CLOUD_PROVIDER="${2:-$CLOUD_PROVIDER_AWS_S3}"
if [ "${CLOUD_PROVIDER}" != "${CLOUD_PROVIDER_AWS_S3}" ] && [ "${CLOUD_PROVIDER}" != "${CLOUD_PROVIDER_GENERIC_S3}" ]; then
echo "${IMAGE_TYPE} can only be uploaded to either ${CLOUD_PROVIDER_AWS_S3} or ${CLOUD_PROVIDER_GENERIC_S3}"
exit 1
fi
;;
*)
echo "Unknown image type: ${IMAGE_TYPE}"
exit 1
esac
ARTIFACTS="${ARTIFACTS:-/tmp/artifacts}"
source /usr/libexec/osbuild-composer-test/set-env-variables.sh
source /usr/libexec/tests/osbuild-composer/shared_lib.sh
export DISTRO="${DISTRO_CODE}"
# Container image used for cloud provider CLI tools
export CONTAINER_IMAGE_CLOUD_TOOLS="quay.io/osbuild/cloud-tools:latest"
WORKDIR=$(mktemp -d)
KILL_PIDS=()
function cleanups() {
greenprint "Cleaning up"
set +eu
cleanup
# dump the DB here to ensure that it gets dumped even if the test fails
dump_db
sudo "${CONTAINER_RUNTIME}" kill "${DB_CONTAINER_NAME}"
sudo "${CONTAINER_RUNTIME}" rm "${DB_CONTAINER_NAME}"
sudo rm -rf "$WORKDIR"
for P in "${KILL_PIDS[@]}"; do
sudo pkill -P "$P"
done
set -eu
}
trap cleanups EXIT
#
# Provision the software under test.
#
# Path to a file with extra composer configuration
EXTRA_COMPOSER_CONF=""
# Configure distro alias for the host distro in composer without the minor
# version.
if [[ "${TEST_DISTRO_ALIAS}" == "1" ]]; then
DISTRO_ALIAS="${ID}-${VERSION_ID%.*}"
EXTRA_COMPOSER_CONF="$(mktemp -p "${WORKDIR}")"
cat <<EOF | tee "${EXTRA_COMPOSER_CONF}" >/dev/null
[distro_aliases]
${DISTRO_ALIAS} = "${DISTRO}"
EOF
fi
/usr/libexec/osbuild-composer-test/provision.sh tls "${EXTRA_COMPOSER_CONF}"
#
# Set up the database queue
#
if which podman 2>/dev/null >&2; then
CONTAINER_RUNTIME=podman
elif which docker 2>/dev/null >&2; then
CONTAINER_RUNTIME=docker
else
echo No container runtime found, install podman or docker.
exit 2
fi
# Start the db
DB_CONTAINER_NAME="osbuild-composer-db"
sudo "${CONTAINER_RUNTIME}" run -d --name "${DB_CONTAINER_NAME}" \
--health-cmd "pg_isready -U postgres -d osbuildcomposer" --health-interval 2s \
--health-timeout 2s --health-retries 10 \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=foobar \
-e POSTGRES_DB=osbuildcomposer \
-p 5432:5432 \
quay.io/osbuild/postgres:13-alpine
# Dump the logs once to have a little more output
sudo "${CONTAINER_RUNTIME}" logs osbuild-composer-db
# Initialize a module in a temp dir so we can get tern without introducing
# vendoring inconsistency
pushd "$(mktemp -d)"
sudo dnf install -y go
go mod init temp
go install github.com/jackc/tern@latest
PGUSER=postgres PGPASSWORD=foobar PGDATABASE=osbuildcomposer PGHOST=localhost PGPORT=5432 \
"$(go env GOPATH)"/bin/tern migrate -m /usr/share/tests/osbuild-composer/schemas
popd
# TODO: move this to the tools/provision.sh and don't override composer config
COMPOSER_CONFIG="/etc/osbuild-composer/osbuild-composer.toml"
cat <<EOF | sudo tee "${COMPOSER_CONFIG}"
log_level = "debug"
[koji]
allowed_domains = [ "localhost", "client.osbuild.org" ]
ca = "/etc/osbuild-composer/ca-crt.pem"
[worker]
allowed_domains = [ "localhost", "worker.osbuild.org" ]
ca = "/etc/osbuild-composer/ca-crt.pem"
pg_host = "localhost"
pg_port = "5432"
pg_database = "osbuildcomposer"
pg_user = "postgres"
pg_password = "foobar"
pg_ssl_mode = "disable"
pg_max_conns = 10
EOF
if [[ "${TEST_DISTRO_ALIAS}" == "1" ]]; then
cat "${EXTRA_COMPOSER_CONF}" | sudo tee -a "${COMPOSER_CONFIG}"
fi
sudo systemctl restart osbuild-composer
greenprint "Using Cloud Provider / Target ${CLOUD_PROVIDER} for Image Type ${IMAGE_TYPE}"
# Load a correct test runner.
# Each one must define following methods:
# - checkEnv
# - cleanup
# - createReqFile
# - installClient
# - checkUploadStatusOptions
case $CLOUD_PROVIDER in
"$CLOUD_PROVIDER_AWS")
source /usr/libexec/tests/osbuild-composer/api/aws.sh
;;
"$CLOUD_PROVIDER_AWS_S3")
source /usr/libexec/tests/osbuild-composer/api/aws.s3.sh
;;
"$CLOUD_PROVIDER_GENERIC_S3")
source /usr/libexec/tests/osbuild-composer/api/generic.s3.sh
;;
"$CLOUD_PROVIDER_GCP")
source /usr/libexec/tests/osbuild-composer/api/gcp.sh
;;
"$CLOUD_PROVIDER_AZURE")
source /usr/libexec/tests/osbuild-composer/api/azure.sh
;;
"$CLOUD_PROVIDER_OCI")
source /usr/libexec/tests/osbuild-composer/api/oci.sh
;;
"$CLOUD_PROVIDER_CONTAINER_IMAGE_REGISTRY")
source /usr/libexec/tests/osbuild-composer/api/container.registry.sh
;;
*)
echo "Unknown cloud provider: ${CLOUD_PROVIDER}"
exit 1
esac
# Verify that this script is running in the right environment.
greenprint "Verifying environment"
checkEnv
# Check that needed variables are set to register to RHSM (RHEL only)
[[ "$ID" == "rhel" ]] && printenv API_TEST_SUBSCRIPTION_ORG_ID API_TEST_SUBSCRIPTION_ACTIVATION_KEY_V2 > /dev/null
function dump_db() {
# Save the result, including the manifest, for the job, straight from the db
sudo "${CONTAINER_RUNTIME}" exec "${DB_CONTAINER_NAME}" psql -U postgres -d osbuildcomposer -c "SELECT result FROM jobs WHERE type='manifest-id-only'" \
| sudo tee "${ARTIFACTS}/build-result.txt"
}
# make a dummy rpm and repo to test payload_repositories
greenprint "Setting up dummy rpm and repo"
sudo dnf install -y rpm-build createrepo
DUMMYRPMDIR=$(mktemp -d)
DUMMYSPECFILE="$DUMMYRPMDIR/dummy.spec"
export PAYLOAD_REPO_PORT="9999"
export PAYLOAD_REPO_URL="http://localhost:9999"
pushd "$DUMMYRPMDIR"
cat <<EOF > "$DUMMYSPECFILE"
#----------- spec file starts ---------------
Name: dummy
Version: 1.0.0
Release: 0
BuildArch: noarch
Vendor: dummy
Summary: Provides %{name}
License: BSD
Provides: dummy
%description
%{summary}
%files
EOF
mkdir -p "DUMMYRPMDIR/rpmbuild"
rpmbuild --quiet --define "_topdir $DUMMYRPMDIR/rpmbuild" -bb "$DUMMYSPECFILE"
mkdir -p "$DUMMYRPMDIR/repo"
cp "$DUMMYRPMDIR"/rpmbuild/RPMS/noarch/*rpm "$DUMMYRPMDIR/repo"
pushd "$DUMMYRPMDIR/repo"
createrepo .
sudo python3 -m http.server "$PAYLOAD_REPO_PORT" &
KILL_PIDS+=("$!")
popd
popd
#
# Install the necessary cloud provider client tools
#
greenprint "Installing cloud provider client tools"
installClient
#
# Make sure /openapi and endpoints return success
#
curl \
--silent \
--show-error \
--cacert /etc/osbuild-composer/ca-crt.pem \
--key /etc/osbuild-composer/client-key.pem \
--cert /etc/osbuild-composer/client-crt.pem \
https://localhost/api/image-builder-composer/v2/openapi | jq .
#
# Prepare a request to be sent to the composer API.
#
REQUEST_FILE="${WORKDIR}/compose_request.json"
IMG_COMPOSE_REQ_FILE="${WORKDIR}/img_compose_request.json"
ARCH=$(uname -m)
SSH_USER=
TEST_ID="$(uuidgen)"
# Generate a string, which can be used as a predictable resource name,
# especially when running the test in CI where we may need to clean up
# resources in case the test unexpectedly fails or is canceled
CI="${CI:-false}"
if [[ "$CI" == true ]]; then
# in CI, imitate GenerateCIArtifactName() from internal/test/helpers.go
TEST_ID="$DISTRO_CODE-$ARCH-$CI_COMMIT_BRANCH-$CI_JOB_ID"
fi
export TEST_ID
if [[ "$ID" == "fedora" ]]; then
# fedora uses fedora for everything
SSH_USER="fedora"
elif [[ "$CLOUD_PROVIDER" == "$CLOUD_PROVIDER_AWS" ]]; then
# RHEL and centos use ec2-user for AWS
SSH_USER="ec2-user"
else
# RHEL and centos use cloud-user for other clouds
SSH_USER="cloud-user"
fi
export SSH_USER
if [[ "$TEST_NO_DOT_NOTATION" == "1" ]]; then
# This removes dot from VERSION_ID.
# ID == rhel && VERSION_ID == 8.6 => DISTRO == rhel-86
# ID == centos && VERSION_ID == 8 => DISTRO == centos-8
# ID == fedora && VERSION_ID == 35 => DISTRO == fedora-35
DISTRO_NAME="$ID-${VERSION_ID//./}"
elif [[ "$TEST_DISTRO_ALIAS" == "1" ]]; then
DISTRO_NAME="$DISTRO_ALIAS"
else
DISTRO_NAME="$DISTRO"
fi
export DISTRO_NAME
SUBSCRIPTION_BLOCK=
# Only RHEL need subscription block.
if [[ "$ID" == "rhel" ]]; then
SUBSCRIPTION_BLOCK=$(cat <<EndOfMessage
,
"subscription": {
"organization": "${API_TEST_SUBSCRIPTION_ORG_ID:-}",
"activation_key": "${API_TEST_SUBSCRIPTION_ACTIVATION_KEY_V2:-}",
"base_url": "https://cdn.redhat.com/",
"server_url": "subscription.rhsm.redhat.com",
"insights": true
}
EndOfMessage
)
fi
export SUBSCRIPTION_BLOCK
# Define the customizations for the images here to not have to repeat them
# in every image-type specific file.
case "${IMAGE_TYPE}" in
# The Directories and Files customization is not supported for this image type.
"$IMAGE_TYPE_EDGE_INSTALLER")
DIR_FILES_CUSTOMIZATION_BLOCK=
;;
*)
DIR_FILES_CUSTOMIZATION_BLOCK=$(cat <<EOF
,
"directories": [
{
"path": "/etc/custom_dir/dir1",
"user": "root",
"group": "root",
"mode": "0775",
"ensure_parents": true
},
{
"path": "/etc/custom_dir2"
}
],
"files": [
{
"path": "/etc/custom_dir/custom_file.txt",
"data": "image builder is the best\n"
},
{
"path": "/etc/custom_dir2/empty_file.txt"
}
]
EOF
)
;;
esac
export DIR_FILES_CUSTOMIZATION_BLOCK
# Define the customizations for the images here to not have to repeat them
# in every image-type specific file.
case "${IMAGE_TYPE}" in
# The Directories and Files customization is not supported for this image type.
"$IMAGE_TYPE_EDGE_INSTALLER")
CUSTOM_GPG_KEY=
REPOSITORY_CUSTOMIZATION_BLOCK=
;;
*)
CUSTOM_GPG_KEY="-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQGiBGRBSJURBACzCoe9UNfxOUiFLq9b60weSBFdr39mLViscecDWATNvXtgRoK/\nxl/4qpayzALRCQ2Ek/pMrbKPF/3ngECuBv7S+rI4n/rIia4FNcqzYeZAz4DE4NP/\neUGvz49tWhmH17hX/rmF9kz5kLq2bDZI4GDgZW/oMDdt2ivj092Ljm9jRwCgyQy3\nWEK6RJvIcSEh9vbdwVdMPOcD/iHqNejTMFwGyZfCWB0eIOoxUOUn/ZZpELTL2UpW\nGduCf3txb5SkK7M+WDbb0S5IvNXoi0tc13STiD6Oxg2O9PkSvvYb+8zxlhNoSTwy\n54j7Rf5FlnQ3TAFfjtQ5LCx56LKK73j4RjvKW//ktm5n54exsgo9Ry/e12T46dRg\n7tIlA/91rzLm57Qyc73A7zjgIzef9O6V5ZzowC+pp/jfb5pS9hXgROekLkMgX0vg\niA5rM5OpqK4bArVP1lRWnLyvghwO+TW763RVuXlS0scfzMy4g0NgrG6j7TIOKEqz\n4xQxOuwkudqiQr/kOqKuLxQBXa+5MJkyhfPmqYw5wpqyCwFa/7Q4b3NidWlsZCB0\nZXN0IChvc2J1aWxkIHRlc3QgZ3Bna2V5KSA8b3NidWlsZEBleGFtcGxlLmNvbT6I\newQTEQIAOxYhBGB8woiEPRKBO8Cr31lulpQgMejzBQJkQUiVAhsjBQsJCAcCAiIC\nBhUKCQgLAgQWAgMBAh4HAheAAAoJEFlulpQgMejzapMAoLmUg1mNDTRUaCrN/fzm\nHYLHL6jkAJ9pEKkJQiHB6SfD0fkiD2GkELYLubkBDQRkQUiVEAQAlAAXrQ572vuw\nxI3W8GSZmOQiAYOQmOKRloLEy6VZ3NSOb9y2TXj33QTkJBPOM17AzB7E+YjZrpUt\ngl6LlXmfjMcJAcXhFaUBCilAcMwMlLl7DtnSkLnLIXYmHiN0v83BH/H0EPutOc5l\n0QIyugutifp9SJz2+EWpC4bjA7GFkQ8AAwUD/1tLEGqCJ37O8gfzYt2PWkqBEoOY\n0Z3zwVS6PWW/IIkak9dAJ0iX5NMeFWpzFNfviDPHqhEdUR55zsxyUZIZlCX5jwmA\nt7qm3cbH4HNU1Ogq3Q9hykbTPWPZVkpvNm/TO8TA2brhkz3nuS8Hbmh+rjXFOSZj\nDQBUxItuuj2hhpQEiGAEGBECACAWIQRgfMKIhD0SgTvAq99ZbpaUIDHo8wUCZEFI\nlQIbDAAKCRBZbpaUIDHo83fQAKDHgFIaggaNsvDQkj7vMX0fecHRhACfS9Bvxn2W\nWSb6T+gChmYBseZwk/k=\n=DQ3i\n-----END PGP PUBLIC KEY BLOCK-----"
REPOSITORY_CUSTOMIZATION_BLOCK=$(cat <<EOF
,
"custom_repositories": [{
"id": "example",
"name": "Example repo",
"baseurl": [ "http://example.com" ],
"gpgkey": [ "$CUSTOM_GPG_KEY" ],
"check_gpg": true,
"enabled": true
}]
EOF
)
;;
esac
export CUSTOM_GPG_KEY
export REPOSITORY_CUSTOMIZATION_BLOCK
# Define the customizations for the images here to not have to repeat them
# in every image-type specific file.
case "${IMAGE_TYPE}" in
# The Directories and Files customization is not supported for this image type.
"$IMAGE_TYPE_EDGE_INSTALLER")
OPENSCAP_CUSTOMIZATION_BLOCK=
;;
*)
OPENSCAP_CUSTOMIZATION_BLOCK=$(cat <<EOF
,
"openscap": {
"profile_id": "pci-dss",
"tailoring": {
"unselected": [ "rpm_verify_permissions" ]
}
}
EOF
)
;;
esac
export OPENSCAP_CUSTOMIZATION_BLOCK
TIMEZONE_CUSTOMIZATION_BLOCK=$(cat <<EOF
,
"timezone": {
"timezone": "Europe/Prague"
}
EOF
)
export TIMEZONE_CUSTOMIZATION_BLOCK
FIREWALL_CUSTOMIZATION_BLOCK=$(cat <<EOF
,
"firewall": {
"services": {
"enabled": ["nfs"]
}
}
EOF
)
export FIREWALL_CUSTOMIZATION_BLOCK
if [ "$TEST_MODULE_HOTFIXES" = "1" ]; then
if [ "$ARCH" = "x86_64" ]; then
NGINX_REPO_URL="https://rpmrepo.osbuild.org/v2/mirror/public/el8/el8-x86_64-nginx-20231207"
else
NGINX_REPO_URL="https://rpmrepo.osbuild.org/v2/mirror/public/el8/el8-aarch64-nginx-20231207"
fi
EXTRA_PAYLOAD_REPOS_BLOCK=$(cat <<EOF
,
{
"baseurl": "$NGINX_REPO_URL",
"check_gpg": false,
"check_repo_gpg": false,
"rhsm": false,
"module_hotfixes": true
}
EOF
)
EXTRA_PACKAGES_BLOCK=$(cat <<EOF
,
"nginx",
"nginx-module-njs"
EOF
)
else
EXTRA_PAYLOAD_REPOS_BLOCK=""
EXTRA_PACKAGES_BLOCK=""
fi
export EXTRA_PAYLOAD_REPOS_BLOCK
export EXTRA_PACKAGES_BLOCK
# generate a temp key for user tests
ssh-keygen -t rsa-sha2-512 -f "${WORKDIR}/usertest" -C "usertest" -N ""
greenprint "Creating request file"
createReqFile
#
# Send the request and wait for the job to finish.
#
# Separate `curl` and `jq` commands here, because piping them together hides
# the server's response in case of an error.
#
function collectMetrics(){
METRICS_OUTPUT=$(curl http://localhost:8008/metrics)
echo "$METRICS_OUTPUT" | grep "^image_builder_composer_request_count.*path=\"/api/image-builder-composer/v2/compose\"" | cut -f2 -d' '
}
function sendCompose() {
echo "Compose request:"
cat "$1"
OUTPUT=$(mktemp)
HTTPSTATUS=$(curl \
--silent \
--show-error \
--cacert /etc/osbuild-composer/ca-crt.pem \
--key /etc/osbuild-composer/client-key.pem \
--cert /etc/osbuild-composer/client-crt.pem \
--header 'Content-Type: application/json' \
--request POST \
--data @"$1" \
--write-out '%{http_code}' \
--output "$OUTPUT" \
https://localhost/api/image-builder-composer/v2/compose)
test "$HTTPSTATUS" = "201"
COMPOSE_ID=$(jq -r '.id' "$OUTPUT")
}
function waitForState() {
local DESIRED_STATE="${1:-success}"
while true
do
OUTPUT=$(curl \
--silent \
--show-error \
--cacert /etc/osbuild-composer/ca-crt.pem \
--key /etc/osbuild-composer/client-key.pem \
--cert /etc/osbuild-composer/client-crt.pem \
"https://localhost/api/image-builder-composer/v2/composes/$COMPOSE_ID")
COMPOSE_STATUS=$(echo "$OUTPUT" | jq -r '.image_status.status')
UPLOAD_STATUS=$(echo "$OUTPUT" | jq -r '.image_status.upload_status.status')
UPLOAD_TYPE=$(echo "$OUTPUT" | jq -r '.image_status.upload_status.type')
UPLOAD_OPTIONS=$(echo "$OUTPUT" | jq -r '.image_status.upload_status.options')
# get the upload_statuses array to compare the first element with the top level upload_status
UPLOAD_STATUSES=$(echo "$OUTPUT" | jq -r '.image_status.upload_statuses')
case "$COMPOSE_STATUS" in
"$DESIRED_STATE")
break
;;
# all valid status values for a compose which hasn't finished yet
"pending"|"building"|"uploading"|"registering")
;;
# default undesired state
"failure")
echo "Image compose failed"
exit 1
;;
*)
echo "API returned unexpected image_status.status value: '$COMPOSE_STATUS'"
exit 1
;;
esac
sleep 30
done
# export for use in subcases
export UPLOAD_OPTIONS
}
function sendImgFromCompose() {
OUTPUT=$(mktemp)
HTTPSTATUS=$(curl \
--silent \
--show-error \
--cacert /etc/osbuild-composer/ca-crt.pem \
--key /etc/osbuild-composer/client-key.pem \
--cert /etc/osbuild-composer/client-crt.pem \
--header 'Content-Type: application/json' \
--request POST \
--data @"$1" \
--write-out '%{http_code}' \
--output "$OUTPUT" \
https://localhost/api/image-builder-composer/v2/composes/"$COMPOSE_ID"/clone)
test "$HTTPSTATUS" = "201"
IMG_ID=$(jq -r '.id' "$OUTPUT")
}
function waitForImgState() {
while true
do
OUTPUT=$(curl \
--silent \
--show-error \
--cacert /etc/osbuild-composer/ca-crt.pem \
--key /etc/osbuild-composer/client-key.pem \
--cert /etc/osbuild-composer/client-crt.pem \
"https://localhost/api/image-builder-composer/v2/clones/$IMG_ID")
IMG_UPLOAD_STATUS=$(echo "$OUTPUT" | jq -r '.status')
IMG_UPLOAD_OPTIONS=$(echo "$OUTPUT" | jq -r '.options')
case "$IMG_UPLOAD_STATUS" in
"success")
break
;;
# all valid status values for a compose which hasn't finished yet
"pending"|"running")
;;
# default undesired state
"failure")
echo "Image compose failed"
exit 1
;;
*)
echo "API returned unexpected image status value: '$IMG_UPLOAD_STATUS'"
exit 1
;;
esac
sleep 30
done
# export for use in subcases
export IMG_UPLOAD_OPTIONS
}
#
# Make sure that requesting a non existing paquet results in failure
# and if TEST_MODULE_HOTFIXES flag is passed, we verify, that without that flag the resolve fails
#
REQUEST_FILE2="${WORKDIR}/request2.json"
jq '.customizations.packages = [ "jesuisunpaquetquinexistepas" ]' "$REQUEST_FILE" > "$REQUEST_FILE2"
greenprint "Sending compose: Fail test"
sendCompose "$REQUEST_FILE2"
waitForState "failure"
if [ "$TEST_MODULE_HOTFIXES" = "1" ]; then
cat "$REQUEST_FILE"
jq 'del(.customizations.payload_repositories[] | select(.baseurl | match(".*public/el8/el8-.*-nginx-.*")) | .module_hotfixes)' "$REQUEST_FILE" > "$REQUEST_FILE2"
greenprint "Sending compose: Fail depsolve test"
sendCompose "$REQUEST_FILE2"
waitForState "failure"
fi
# crashed/stopped/killed worker should result in the job being retried
greenprint "Sending compose: Retry test"
sendCompose "$REQUEST_FILE"
waitForState "building"
sudo systemctl stop "osbuild-remote-worker@*"
RETRIED=0
for RETRY in {1..10}; do
ROWS=$(sudo "${CONTAINER_RUNTIME}" exec "${DB_CONTAINER_NAME}" psql -U postgres -d osbuildcomposer -c \
"SELECT retries FROM jobs WHERE id = '$COMPOSE_ID' AND retries = 1")
if grep -q "1 row" <<< "$ROWS"; then
RETRIED=1
break
else
echo "Waiting until job is retried ($RETRY/10)"
sleep 30
fi
done
if [ "$RETRIED" != 1 ]; then
echo "Job $COMPOSE_ID wasn't retried after killing the worker"
exit 1
fi
# remove the job from the queue so the worker doesn't pick it up again
sudo "${CONTAINER_RUNTIME}" exec "${DB_CONTAINER_NAME}" psql -U postgres -d osbuildcomposer -c \
"DELETE FROM jobs WHERE id = '$COMPOSE_ID'"
sudo systemctl start "osbuild-remote-worker@localhost:8700.service"
# full integration case
greenprint "Sending compose: Full test"
INIT_COMPOSES="$(collectMetrics)"
sendCompose "$REQUEST_FILE"
waitForState
SUBS_COMPOSES="$(collectMetrics)"
test "$UPLOAD_STATUS" = "success"
EXPECTED_UPLOAD_TYPE="$CLOUD_PROVIDER"
if [ "${CLOUD_PROVIDER}" == "${CLOUD_PROVIDER_GENERIC_S3}" ]; then
EXPECTED_UPLOAD_TYPE="${CLOUD_PROVIDER_AWS_S3}"
fi
if [ "${CLOUD_PROVIDER}" == "${CLOUD_PROVIDER_OCI}" ]; then
EXPECTED_UPLOAD_TYPE="oci.objectstorage"
fi
test "$UPLOAD_TYPE" = "$EXPECTED_UPLOAD_TYPE"
test $((INIT_COMPOSES+1)) = "$SUBS_COMPOSES"
# test that the first element in the upload_statuses matches the top
# upload_status
UPLOAD_STATUS_0=$(echo "$UPLOAD_STATUSES" | jq -r '.[0].status')
test "$UPLOAD_STATUS_0" = "success"
UPLOAD_TYPE_0=$(echo "$UPLOAD_STATUSES" | jq -r '.[0].type')
test "$UPLOAD_TYPE" = "$UPLOAD_TYPE_0"
UPLOAD_OPTIONS_0=$(echo "$UPLOAD_STATUSES" | jq -r '.[0].options')
test "$UPLOAD_OPTIONS" = "$UPLOAD_OPTIONS_0"
if [ -s "$IMG_COMPOSE_REQ_FILE" ]; then
sendImgFromCompose "$IMG_COMPOSE_REQ_FILE"
waitForImgState
fi
#
# Verify the Cloud-provider specific upload_status options
#
greenprint "Checking upload status options"
checkUploadStatusOptions
#
# Verify the image landed in the appropriate cloud provider, and delete it.
#
greenprint "Verifying image upload"
verify
# Verify selected package (postgresql) is included in package list
function verifyPackageList() {
# Save build metadata to artifacts directory for troubleshooting
curl --silent \
--show-error \
--cacert /etc/osbuild-composer/ca-crt.pem \
--key /etc/osbuild-composer/client-key.pem \
--cert /etc/osbuild-composer/client-crt.pem \
https://localhost/api/image-builder-composer/v2/composes/"$COMPOSE_ID"/metadata --output "${ARTIFACTS}/metadata.json"
local PACKAGENAMES
PACKAGENAMES=$(jq -rM '.packages[].name' "${ARTIFACTS}/metadata.json")
if ! grep -q postgresql <<< "${PACKAGENAMES}"; then
echo "'postgresql' not found in compose package list 😠"
exit 1
fi
}
greenprint "Verifying package list"
verifyPackageList
#
# Verify oauth2
#
greenprint "Verifying oauth2"
cat <<EOF | sudo tee "/etc/osbuild-composer/osbuild-composer.toml"
[koji]
enable_tls = false
enable_mtls = false
enable_jwt = true
jwt_keys_urls = ["https://localhost:8080/certs"]
jwt_ca_file = "/etc/osbuild-composer/ca-crt.pem"
jwt_acl_file = ""
jwt_tenant_provider_fields = ["rh-org-id"]
[worker]
pg_host = "localhost"
pg_port = "5432"
enable_artifacts = false
pg_database = "osbuildcomposer"
pg_user = "postgres"
pg_password = "foobar"
pg_ssl_mode = "disable"
enable_tls = true
enable_mtls = false
enable_jwt = true
jwt_keys_urls = ["https://localhost:8080/certs"]
jwt_ca_file = "/etc/osbuild-composer/ca-crt.pem"
jwt_tenant_provider_fields = ["rh-org-id"]
EOF
REFRESH_TOKEN="offlineToken"
cat <<EOF | sudo tee "/etc/osbuild-worker/token"
$REFRESH_TOKEN
EOF
cat <<EOF | sudo tee "/etc/osbuild-worker/osbuild-worker.toml"
[authentication]
oauth_url = http://localhost:8081/token
client_id = "rhsm-api"
offline_token = "/etc/osbuild-worker/token"
EOF
# Spin up an https instance for the composer-api and worker-api; the auth handler needs to hit an ssl `/certs` endpoint
sudo /usr/libexec/osbuild-composer-test/osbuild-mock-openid-provider -rsaPubPem /etc/osbuild-composer/client-crt.pem -rsaPem /etc/osbuild-composer/client-key.pem -cert /etc/osbuild-composer/composer-crt.pem -key /etc/osbuild-composer/composer-key.pem &
KILL_PIDS+=("$!")
# Spin up an http instance for the worker client to bypass the need to specify an extra CA
sudo /usr/libexec/osbuild-composer-test/osbuild-mock-openid-provider -a localhost:8081 -rsaPubPem /etc/osbuild-composer/client-crt.pem -rsaPem /etc/osbuild-composer/client-key.pem &
KILL_PIDS+=("$!")
sudo systemctl restart osbuild-composer
until curl --data "grant_type=refresh_token" --output /dev/null --silent --fail localhost:8081/token; do
sleep 0.5
done
TOKEN="$(curl --request POST \
--data "grant_type=refresh_token" \
--data "refresh_token=$REFRESH_TOKEN" \
--header "Content-Type: application/x-www-form-urlencoded" \
--silent \
--show-error \
--fail \
localhost:8081/token | jq -r .access_token)"
[ "$(curl \
--silent \
--output /dev/null \
--write-out '%{http_code}' \
--header "Authorization: Bearer $TOKEN" \
http://localhost:443/api/image-builder-composer/v2/openapi)" = "200" ]
# /openapi doesn't need auth
[ "$(curl \
--silent \
--output /dev/null \
--write-out '%{http_code}' \
--header "Authorization: Bearer badtoken" \
http://localhost:443/api/image-builder-composer/v2/openapi)" = "200" ]
# /composes/$ID does need auth
[ "$(curl \
--silent \
--output /dev/null \
--write-out '%{http_code}' \
--header "Authorization: Bearer badtoken" \
http://localhost:443/api/image-builder-composer/v2/composes/"$COMPOSE_ID")" = "401" ]
sudo systemctl restart osbuild-remote-worker@localhost:8700.service
sudo systemctl is-active --quiet osbuild-remote-worker@localhost:8700.service
greenprint "DONE"
exit 0