#!/bin/bash DEBUG="${DEBUG:-false}" PROMPT="${PROMPT:-true}" KONNECT_DOMAIN="${KONNECT_DOMAIN:-konghq.com}" export APP_NAME="kong-ai-quickstart" export OUTPUT_DIR="/tmp/kong/${APP_NAME}" export LOG_FILE="${OUTPUT_DIR}/ai-gateway.log" mkdir -p "${OUTPUT_DIR}" GET_KONGHQ_COM_URL="https://get.konghq.com" QUICKSTART_SCRIPT_PATH="${OUTPUT_DIR}/quickstart" DEFAULT_KONNECT_AI_GATEWAY_NAME="ai-gateway-quickstart" export KONG_PRODUCT="Kong AI Gateway" export KONG_IMAGE_REPO="kong" export KONG_IMAGE_NAME="kong-ai-gateway-dev" export KONG_IMAGE_TAG="2.0.1-rc.3" download_quickstart_script() { if [ "${DEBUG}" = "false" ]; then echo "Downloading quickstart script to ${QUICKSTART_SCRIPT_PATH}" >> "${LOG_FILE}" curl -s -f -o "${QUICKSTART_SCRIPT_PATH}" "${GET_KONGHQ_COM_URL}/quickstart" || \ return 1 else echo "Copying local quickstart script to ${QUICKSTART_SCRIPT_PATH}" >> "${LOG_FILE}" cp ./quickstart "${QUICKSTART_SCRIPT_PATH}" fi } ai_gateway_main() { download_quickstart_script || { echo "Failed to download quickstart script" >> "${LOG_FILE}" return 1 } source "${QUICKSTART_SCRIPT_PATH}" --source echo "Sourced ${QUICKSTART_SCRIPT_PATH}" >> "${LOG_FILE}" # Must be set after source — quickstart assigns DEFAULT_KONNECT_CONTROL_PLANE_NAME="quickstart". # customize_defaults is called by quickstart's main() after getopts, so APP_NAME already # reflects -a by the time it runs. The guard avoids changing the CP default when the user # didn't pass -a (APP_NAME stays as its initial value). display_dataplane_hook() { echo "export AI_GATEWAY_ID=\"${CONTROL_PLANE_ID}\"" } local _default_app_name="${APP_NAME}" customize_defaults() { [[ "${APP_NAME}" != "${_default_app_name}" ]] && \ DEFAULT_KONNECT_CONTROL_PLANE_NAME="${APP_NAME}" } DEFAULT_KONNECT_CONTROL_PLANE_NAME="ai-quickstart" fetch_konnect_cp() { echo ">fetch_konnect_cp" >> $LOG_FILE local cp_name="${1}" local page=1 rv=1 local response parsed match has_more # TODO: filter by name when the API supports it while true; do response=$(curl -s --fail-with-body --request GET \ --header "Authorization: Bearer ${KONNECT_PAT}" \ --url "https://${KONNECT_REGION}.api.${KONNECT_DOMAIN}/v1/ai-gateways?page\[number\]=${page}&page\[size\]=100" \ --header 'accept: application/json') rv=$? echo "${response}" >> $LOG_FILE [[ $rv -ne 0 ]] && break # Single jq pass: compact output keeps the object on one line so # bash parameter expansion can split match (line 1) from has_more (line 2). parsed=$(echo "${response}" | docker run -i --rm ${JQ_IMAGE} -rc \ --arg name "${cp_name}" \ '(.data | map(select((.name // .display_name) == $name)) | .[0]) // null, ((.meta.page.number * .meta.page.size) < .meta.page.total)') match="${parsed%%$'\n'*}" has_more="${parsed##*$'\n'}" if [[ -n "${match}" && "${match}" != "null" ]]; then CONTROL_PLANE_INFO="${match}" rv=0 break fi [[ "${has_more}" == "true" ]] || break page=$((page + 1)) done [[ $rv -eq 0 ]] || CONTROL_PLANE_INFO="" echo "> $LOG_FILE return $rv } create_konnect_cp() { echo ">create_konnect_cp" >> $LOG_FILE local cp_name="${1}" local cp_description="${2}" local body="{\"name\":\"$cp_name\",\"display_name\":\"$cp_name\",\"description\":\"$cp_description\"}" local response response=$(curl -s --fail-with-body \ --header "Authorization: Bearer ${KONNECT_PAT}" \ --url "https://${KONNECT_REGION}.api.${KONNECT_DOMAIN}/v1/ai-gateways" \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --data "${body}") local rv=$? if [[ $rv -ne 0 ]]; then echo "${response}" >> $LOG_FILE echo_fail "Unable to create control plane" exit 1 else CONTROL_PLANE_INFO="${response}" fi echo "> $LOG_FILE return $rv } parse_konnect_cp() { echo ">parse_konnect_cp" >> $LOG_FILE local parsed_output parsed_output=$(echo "${CONTROL_PLANE_INFO}" | docker run -i --rm ${JQ_IMAGE} -r '.id, (.name // .display_name), .endpoints.configuration, .endpoints.telemetry, .description') CONTROL_PLANE_ID=$(echo "$parsed_output" | sed -n '1p') CONTROL_PLANE_NAME=$(echo "$parsed_output" | sed -n '2p') CONTROL_PLANE_ENDPOINT=$(echo "$parsed_output" | sed -n '3p') CONTROL_PLANE_TELEMETRY_ENDPOINT=$(echo "$parsed_output" | sed -n '4p') CONTROL_PLANE_DESCRIPTION=$(echo "$parsed_output" | sed -n '5p') echo "Control Plane ID: ${CONTROL_PLANE_ID}" >> $LOG_FILE echo "Control Plane Name: ${CONTROL_PLANE_NAME}" >> $LOG_FILE echo "Control Plane Endpoint: ${CONTROL_PLANE_ENDPOINT}" >> $LOG_FILE echo "Control Plane Telemetry Endpoint: ${CONTROL_PLANE_TELEMETRY_ENDPOINT}" >> $LOG_FILE echo "Control Plane Description: ${CONTROL_PLANE_DESCRIPTION}" >> $LOG_FILE echo "> $LOG_FILE } deploy_konnect_certs() { local cp_id="${1}" echo ">deploy_konnect_certs" >> $LOG_FILE echo "Generating escaped certificate to ${OUTPUT_DIR}/tls.crt.escaped" >> $LOG_FILE awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' "${OUTPUT_DIR}/tls.crt" > "${OUTPUT_DIR}/tls.crt.escaped" echo "Deploying certificate to Konnect Control Plane ${cp_id}" >> $LOG_FILE curl --fail-with-body -s --request POST \ --header "Authorization: Bearer ${KONNECT_PAT}" \ --url "https://${KONNECT_REGION}.api.${KONNECT_DOMAIN}/v1/ai-gateways/${cp_id}/data-plane-certificates" \ --header 'Content-Type: application/json' \ --header 'accept: application/json' \ --data "{\"cert\":\"$(cat ${OUTPUT_DIR}/tls.crt.escaped)\",\"title\":\"${APP_NAME}-certificate\"}" >> $LOG_FILE 2>&1 local rv=$? echo >> $LOG_FILE echo "> $LOG_FILE return $rv } delete_konnect_cp() { local cp_name="${KONNECT_CONTROL_PLANE_NAME:-${DEFAULT_KONNECT_CONTROL_PLANE_NAME}}" echo_wait "Deleting Konnect Control Plane '$cp_name': " noline fetch_konnect_cp "${cp_name}" CP_ID_TO_DELETE=$(echo "${CONTROL_PLANE_INFO}" | docker run -i --rm ${JQ_IMAGE} -r '.id') if [[ -z "$CP_ID_TO_DELETE" || "$CP_ID_TO_DELETE" == "null" ]]; then echo "delete_konnect_cp: no AI Gateway found with name '$cp_name', skipping deletion" >> $LOG_FILE else curl -s -X DELETE \ --header "Authorization: Bearer ${KONNECT_PAT}" \ --url "https://${KONNECT_REGION}.api.${KONNECT_DOMAIN}/v1/ai-gateways/${CP_ID_TO_DELETE}" \ --header 'accept: application/json' >> $LOG_FILE 2>&1 fi echo_pass "" } main "$@" } # If a user wants to source this script they need to provide this argument # otherwise it's a challenge to detect execution vs sourcing in all contexts # (like piping from a curl or cat command) if [ "${1}" != "--source" ]; then echo ai_gateway_main "$@" fi