2024-08-09 09:38:56 -07:00
#!/bin/bash
set -eu # use set -eux for debugging
function load_env_variables( ) {
set -a
source .env
set +a
}
function checkRequiredParams ( ) {
requiredParams = (
LOCATION
RESOURCE_GROUP
SUBSCRIPTION_ID
AAD_CLIENT_ID
AAD_OBJECT_ID
AAD_TENANT_ID
)
local paramsFile = $1
for param in " ${ requiredParams [@] } " ; do
local paramValue = $( jq -r .$param < $paramsFile )
if [ "null" = = " $paramValue " ] || [ -z " $paramValue " ] ; then
echo " Parameter $param is required, exiting... "
exit 1
fi
done
}
function populateRequiredParams ( ) {
local paramsFile = $1
printf "Checking required parameters... "
checkRequiredParams $paramsFile
# The jq command below sets environment variables based on the key-value pairs in a JSON-formatted file
eval $( jq -r 'to_entries | .[] | "export \(.key)=\(.value)"' $paramsFile )
printf "Done.\n"
}
function set_variables( ) {
printf "Setting environment variables...\n"
SUBSCRIPTION_ID = ${ SUBSCRIPTION_ID :- "" }
RESOURCE_GROUP = ${ RESOURCE_GROUP :- "" }
LOCATION = ${ LOCATION :- "" }
AAD_CLIENT_ID = ${ AAD_CLIENT_ID :- "" }
AAD_OBJECT_ID = ${ AAD_OBJECT_ID :- "" }
AAD_TENANT_ID = ${ AAD_TENANT_ID :- "" }
AAD_TOKEN_ISSUER_URL = ${ AAD_TOKEN_ISSUER_URL :- " https://login.microsoftonline.com/ $AAD_TENANT_ID /v2.0 " }
IMAGE_NAME = ${ IMAGE_NAME :- "graphrag:frontend" }
REGISTRY_NAME = ${ REGISTRY_NAME :- " ${ RESOURCE_GROUP } reg " }
APP_SERVICE_PLAN = ${ APP_SERVICE_PLAN :- " ${ RESOURCE_GROUP } -asp " }
WEB_APP = ${ WEB_APP :- " ${ RESOURCE_GROUP } -playground " }
WEB_APP_IDENTITY = ${ WEB_APP_IDENTITY :- " ${ WEB_APP } -identity " }
#BACKEND_RESOURCE_GROUP=${BACKEND_RESOURCE_GROUP:-""} # needed for backend outbound vnet integration
printf "Done setting environment variables.\n"
}
function create_resource_group {
printf " Setting subsctiption to $SUBSCRIPTION_ID and Creating resource group...\n "
az account set --subscription $SUBSCRIPTION_ID > /dev/null
az group create --name $RESOURCE_GROUP --location $LOCATION > /dev/null
printf "Resource group created.\n"
}
function create_acr( ) {
printf "Creating Azure Container Registry...\n"
az acr create --resource-group $RESOURCE_GROUP \
--name $REGISTRY_NAME \
--sku Basic \
2024-10-01 15:44:48 -07:00
--admin-enabled false > /dev/null
2024-08-09 09:38:56 -07:00
printf "Azure Container Registry created.\n"
}
function build_and_push_image( ) {
printf "Building and pushing image...\n"
local SCRIPT_DIR = " $( cd -- " $( dirname -- " ${ BASH_SOURCE [0] :- $0 } " ; ) " & > /dev/null && pwd 2> /dev/null; ) " ;
az acr build --registry $REGISTRY_NAME -f $SCRIPT_DIR /../docker/Dockerfile-frontend --image $IMAGE_NAME $SCRIPT_DIR /../
printf "Image built and pushed.\n"
}
function create_app_service_plan( ) {
printf "Creating app service plan...\n"
az appservice plan create --name $APP_SERVICE_PLAN \
--resource-group $RESOURCE_GROUP \
--sku B3 \
--is-linux > /dev/null
printf "App service plan created.\n"
}
function create_web_app_identity( ) {
printf "Creating web app identity...\n"
IDENTITY_RESULT = $( az identity create --resource-group $RESOURCE_GROUP --name $WEB_APP_IDENTITY --output json)
WEBAPP_IDENTITY_ID = $( jq -r .id <<< $IDENTITY_RESULT )
WEBAPP_IDENTITY_OBJECT_ID = $( jq -r .principalId <<< $IDENTITY_RESULT )
WEBAPP_IDENTITY_CLIENT_ID = $( jq -r .clientId <<< $IDENTITY_RESULT )
printf "Web app identity created.\n"
}
2024-10-01 15:44:48 -07:00
2024-08-09 09:38:56 -07:00
function configure_registry_credentials( ) {
printf "Configuring registry credentials...\n"
ACR_ID = $( az acr show --name $REGISTRY_NAME --resource-group $RESOURCE_GROUP --query id --output tsv)
az role assignment create --assignee $WEBAPP_IDENTITY_CLIENT_ID \
--role AcrPull \
--scope $ACR_ID > /dev/null
printf "Registry credentials configured.\n"
}
2024-10-01 15:44:48 -07:00
function create_web_app( ) {
printf "Creating web app...\n"
az webapp create --resource-group $RESOURCE_GROUP \
--plan $APP_SERVICE_PLAN \
--name $WEB_APP \
--assign-identity $WEBAPP_IDENTITY_ID \
--acr-use-identity \
--acr-identity $WEBAPP_IDENTITY_ID \
--https-only true \
--container-image-name $REGISTRY_NAME .azurecr.io/$IMAGE_NAME > /dev/null
printf "Web app created.\n"
}
2024-08-09 09:38:56 -07:00
function configure_app_settings( ) {
printf "Configuring app settings...\n"
APP_SETTINGS = ""
while IFS = '=' read -r name value
do
value = " ${ value % \" } " # Remove opening quote
value = " ${ value # \" } " # Remove closing quote
APP_SETTINGS = " $APP_SETTINGS $name = $value "
done < .env
# echo $APP_SETTINGS
az webapp config appsettings set --name $WEB_APP \
--resource-group $RESOURCE_GROUP \
--settings $APP_SETTINGS > /dev/null
printf "App settings configured.\n"
}
function create_federated_identity_credentials( ) {
printf "Creating federated identity credentials...\n"
EXISTING_CREDENTIAL_SUBJECTS = $( az rest --method GET --uri " https://graph.microsoft.com/beta/applications/ $AAD_OBJECT_ID /federatedIdentityCredentials " -o json | jq -r '.value[].subject' )
if [ [ " $EXISTING_CREDENTIAL_SUBJECTS " = = *" $WEBAPP_IDENTITY_OBJECT_ID " * ] ] ; then
echo " Federated identity credential already exists for the subject: $WEBAPP_IDENTITY_OBJECT_ID "
else
az webapp auth update \
--name $WEB_APP \
--resource-group $RESOURCE_GROUP \
--enabled true \
--action LoginWithAzureActiveDirectory \
--aad-client-id $AAD_CLIENT_ID \
--aad-token-issuer-url $AAD_TOKEN_ISSUER_URL > /dev/null
az rest --method POST \
--uri " https://graph.microsoft.com/beta/applications/ $AAD_OBJECT_ID /federatedIdentityCredentials " \
--body " {'name': ' $WEB_APP ', 'issuer': ' $AAD_TOKEN_ISSUER_URL ', 'subject': ' $WEBAPP_IDENTITY_OBJECT_ID ', 'audiences': [ 'api://AzureADTokenExchange' ]} " > /dev/null
fi
printf "Federated identity credentials created.\n"
}
function configure_auth_settings( ) {
printf "Configuring auth settings...\n"
az webapp config appsettings set --resource-group $RESOURCE_GROUP \
--name $WEB_APP \
--slot-settings OVERRIDE_USE_MI_FIC_ASSERTION_CLIENTID = $WEBAPP_IDENTITY_CLIENT_ID \
--verbose > /dev/null
az webapp config appsettings list --resource-group $RESOURCE_GROUP \
--name $WEB_APP > /dev/null
authSettings = $( az rest --method GET --url " /subscriptions/ $SUBSCRIPTION_ID /resourceGroups/ $RESOURCE_GROUP /providers/Microsoft.Web/sites/ $WEB_APP /config/authsettingsV2/list?api-version=2020-12-01 " --output json)
echo $authSettings > auth.json
2024-09-12 21:41:46 -04:00
jq '.properties.identityProviders.azureActiveDirectory.registration.clientSecretSettingName = "OVERRIDE_USE_MI_FIC_ASSERTION_CLIENTID"' auth.json > tmp.json && mv tmp.json auth.json # pragma: allowlist secret
2024-08-09 09:38:56 -07:00
az rest --method PUT \
--url " /subscriptions/ $SUBSCRIPTION_ID /resourceGroups/ $RESOURCE_GROUP /providers/Microsoft.Web/sites/ $WEB_APP /config/authsettingsV2?api-version=2020-12-01 " \
--body @auth.json \
--headers "Content-Type=application/json" > /dev/null
rm auth.json
printf "Auth settings configured.\n"
}
function update_appreg_redirect_uris( ) {
printf "Updating app registration redirect URIs...\n"
WEB_APP_URL = $( az webapp show --name $WEB_APP --resource-group $RESOURCE_GROUP --query defaultHostName --output tsv)
NEW_REDIRECT_URI = https://$WEB_APP_URL /.auth/login/aad/callback
# Fetch the current list of web redirect URIs
CURRENT_URIS = $( az ad app show --id $AAD_CLIENT_ID --query "web.redirectUris" --output tsv)
if ! echo " ${ CURRENT_URIS } " | grep -q " ${ NEW_REDIRECT_URI } " ; then
az ad app update --id $AAD_CLIENT_ID --web-redirect-uris ${ CURRENT_URIS [@] } " $NEW_REDIRECT_URI " > /dev/null
fi
printf "App registration redirect URIs updated.\n"
}
function restart_web_app( ) {
printf "Restarting web app...\n"
az webapp restart --name $WEB_APP --resource-group $RESOURCE_GROUP > /dev/null
printf "Waiting for webapp to restart, webapp might take a few minutes to load.....\n"
sleep 180
printf "Web app restarted. \n"
}
## The following function adds outbound vnet integration on the webapp so that the frontend container can access resources in the AKS cluster directly.
## This will create a new subnet named "frontend" in the backend resource group's vnet if it does not exist.
## This may not be needed in simplified backend architecture but useful for folks using a prior version of the accelerator that had a different network architecture.
# function add_vnet_integration() {
# VNET_NAME=$(az network vnet list --resource-group $BACKEND_RESOURCE_GROUP --query "[0].name" --output tsv)
# VNET_ID=$(az network vnet list --resource-group $BACKEND_RESOURCE_GROUP --query "[0].id" --output tsv)
# SUBNET_NAMES=$(az network vnet subnet list --resource-group $BACKEND_RESOURCE_GROUP --vnet-name $VNET_NAME --query "[].name" --output tsv)
# SUBNET_NAME="frontend"
# if [[ $SUBNET_NAMES == *$SUBNET_NAME* ]]; then
# echo "Subnet with name $SUBNET_NAME already exists"
# else
# echo "Subnet with name $SUBNET_NAME does not exist, creating one now."
# az network vnet subnet create --resource-group $BACKEND_RESOURCE_GROUP --vnet-name $VNET_NAME --name $SUBNET_NAME --address-prefixes 10.0.10.0/24
# fi
# az webapp vnet-integration add --name $WEB_APP --resource-group $RESOURCE_GROUP --vnet $VNET_ID --subnet $SUBNET_NAME
# }
function usage( ) {
echo
echo " Usage: bash $0 [-h] -p <frontend_deploy.parameters.json> "
echo "Description: Deployment script for the Frontend App for GraphRAG Solution Accelerator."
echo "options:"
echo " -h Print this help menu."
echo " -p A JSON file containing the deployment parameters (frontend_deploy.parameters.json)."
echo
}
function main( ) {
load_env_variables
populateRequiredParams $PARAMS_FILE
set_variables
create_resource_group
create_acr
build_and_push_image
create_app_service_plan
create_web_app_identity
configure_registry_credentials
2024-10-01 15:44:48 -07:00
create_web_app
2024-08-09 09:38:56 -07:00
configure_app_settings
create_federated_identity_credentials
configure_auth_settings
# add_vnet_integration
update_appreg_redirect_uris
restart_web_app
echo "**********Graphrag Frontend Web app deployment successful!**********"
echo " Please visit the webapp at https:// $WEB_APP_URL "
echo "*******************************************************************"
}
# print usage if no arguments are supplied
[ $# -eq 0 ] && usage && exit 0
PARAMS_FILE = ""
while getopts ":p:h" option; do
case " ${ option } " in
p)
PARAMS_FILE = ${ OPTARG }
; ;
h | *)
usage
exit 0
; ;
esac
done
shift $(( OPTIND-1))
# check if required arguments are supplied
if [ ! -f $PARAMS_FILE ] ; then
echo "Error: invalid required argument."
usage
exit 1
fi
main