Skip to content
This repository was archived by the owner on May 27, 2025. It is now read-only.

Commit a0b0629

Browse files
Adding deployment script for frontend application (#128)
Co-authored-by: Josh Bradley <[email protected]>
1 parent 4319144 commit a0b0629

File tree

3 files changed

+346
-4
lines changed

3 files changed

+346
-4
lines changed

frontend/README.md

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
# Frontend Application Launch Instructions
2-
A small frontend application, a streamlit app, is provided to demonstrate how to build a UI on top of the solution accelerator API.
2+
A small frontend application (a streamlit app) is provided to demonstrate how to build and deploy a UI on top of the solution accelerator API.
3+
This application is optional and not required for the solution accelerator API to function properly.
34

45
### 1. Deploy the GraphRAG solution accelerator
56
Follow instructions from the [deployment guide](../docs/DEPLOYMENT-GUIDE.md) to deploy a full instance of the solution accelerator.
67

78
### 2. (optional) Create a `.env` file:
9+
If a `.env` file is not provided, the UI will prompt the user for additional login information.
810

911
| Variable Name | Required | Example | Description |
1012
| :--- | --- | :--- | ---: |
1113
DEPLOYMENT_URL | No | https://<my_apim>.azure-api.net | Base url of the deployed graphrag API. Also referred to as the APIM Gateway URL.
1214
APIM_SUBSCRIPTION_KEY | No | <subscription_key> | A [subscription key](https://learn.microsoft.com/en-us/azure/api-management/api-management-subscriptions) generated by APIM.
1315
DEPLOYER_EMAIL | No | [email protected] | Email address of the person/organization that deployed the solution accelerator.
1416

15-
### 3. Start UI
17+
## Run UI locally
1618

17-
The frontend application can be run locally as a docker container. If a `.env` file is not provided, the UI will prompt the user for additional information.
19+
The frontend application can run locally as a docker container.
1820

1921
```
2022
# cd to the root directory of the repo
@@ -23,4 +25,59 @@ The frontend application can be run locally as a docker container. If a `.env` f
2325
```
2426
To access the app , visit `localhost:8080` in your browser.
2527

26-
This UI application can also be hosted in Azure as a [Web App](https://azure.microsoft.com/en-us/products/app-service/web).
28+
## Host UI in Azure
29+
The frontend application can also be hosted in Azure as a Web App using the provided `frontend/deploy.sh` script.
30+
31+
### 1. Create Azure App Registration
32+
33+
To enable authentication and authorization for the frontend application, you need to create an Azure App Registration with ID tokens enabled. You may need Owner level permissions on the subscription for some steps.
34+
This app registration will be used for Authentication and Authorization to the frontend web app (not the backend). Follow the steps below:
35+
36+
1. Go to the [Azure portal](https://portal.azure.com) and sign in with your Azure account.
37+
2. Navigate to the **Microsoft Entra ID** service.
38+
3. Select **App registrations** from the left-hand menu.
39+
4. Click on the **+ New registration** button.
40+
5. Provide a name for your app registration and select the appropriate account type.
41+
6. Under the **Redirect URIs** section, select the `Web` platform from dropdown menu. Add `http://localhost:8080/.auth/login/aad/callback` to the URL text field. The deployment script will update this field later with the actual URL of the webapp.
42+
7. Save the app registration.
43+
8. Under the **Manage** section select **Authentication**. Select **ID tokens** as the supported token type.
44+
9. In the **Overview** section of the registered app, take note of the **Application (client) ID**, **Object ID** and **Directory (tenant) ID**. This information will be used later.
45+
46+
### 2. Populate the deploy parameters
47+
48+
Please fill out `frontend/frontend_deploy.parameters.json` with the required values described below.
49+
50+
1. Replace the placeholder values with actual values for the following required variables, you may also add optional variables in the json file if you wish to override the default values:
51+
52+
| Variable Name | Required | Example | Description |
53+
| :------------------- | :------- | :------------------------------------- | :-------------------------------------------------------------- |
54+
| LOCATION | Yes | eastus | The Azure region where resources will be deployed. |
55+
| RESOURCE_GROUP | Yes | my-resource-group | The name of the Azure resource group where resources will be created. At this time, the name must follow [Azure Container Registry](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftcontainerregistry) naming guidelines. |
56+
| SUBSCRIPTION_ID | Yes | 12345678-1234-1234-1234-1234567890ab | The ID of the Azure subscription where the resources will be deployed. |
57+
| AAD_CLIENT_ID | Yes | 12345678-1234-1234-1234-1234567890ab | The client ID of the Microsoft Entra ID (AAD) app registration. |
58+
| AAD_OBJECT_ID | Yes | 12345678-1234-1234-1234-1234567890ab | The object ID of the Microsoft Entra ID (AAD) app registration. |
59+
| AAD_TENANT_ID | Yes | 12345678-1234-1234-1234-1234567890ab | The ID of the Microsoft Entra ID (AAD) tenant. |
60+
| AAD_TOKEN_ISSUER_URL | No | https://login.microsoftonline.com/12345678-1234-1234-1234-1234567890ab/v2.0 | The URL of the Microsoft Entra ID (AAD) token issuer. Defaults to the tenant-specific issuer URL. |
61+
| IMAGE_NAME | No | graphrag:frontend | The name of the Docker image for the frontend application. Defaults to `graphrag:frontend`. |
62+
| REGISTRY_NAME | No | myresourcegroupreg | The name of the Azure Container Registry. Defaults to the resource group name with `reg` appended. |
63+
| APP_SERVICE_PLAN | No | myresourcegroup-asp | The name of the Azure App Service plan. Defaults to the resource group name with `asp` appended. |
64+
| WEB_APP | No | myresourcegroup-playground | The name of the Azure Web App. Defaults to the resource group name with `playground` appended. |
65+
| WEB_APP_IDENTITY | No | myresourcegroup-playground-identity | The name of the managed identity for the Azure Web App. Defaults to the web app name with `identity` appended. |
66+
67+
Save the `frontend/frontend_deploy.parameters.json` file after populating the values. If you would like the webapp to automatically connect
68+
to the solution accelerator backend API, create and populate a `.env` file described in step 2, otherwise the webapp will ask for login credentials to the APIM service that was deployed as part of the backend API.
69+
70+
### 3. Run the deploy script
71+
72+
Prerequisite : Please install az-cli version >=2.61.0
73+
To deploy the frontend application:
74+
75+
1. Open a terminal and navigate to the `<graphrag_repo>/frontend` directory.
76+
2. Run the deploy script:
77+
78+
```
79+
# cd to graphrag-accelerator/frontend directory
80+
> bash deploy.sh -p frontend_deploy.parameters.json
81+
```
82+
83+
One the frontend application has been deployed, please take note of the URL that is displayed at the end of the script. It will have the form `(https://PLACEHOLDER.azurewebsites.net)`. The Web App service will take 2-3 minutes initially to load the first time. This is expected behavior.

frontend/deploy.sh

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
#!/bin/bash
2+
3+
set -eu # use set -eux for debugging
4+
5+
function load_env_variables() {
6+
set -a
7+
source .env
8+
set +a
9+
}
10+
11+
function checkRequiredParams () {
12+
requiredParams=(
13+
LOCATION
14+
RESOURCE_GROUP
15+
SUBSCRIPTION_ID
16+
AAD_CLIENT_ID
17+
AAD_OBJECT_ID
18+
AAD_TENANT_ID
19+
)
20+
local paramsFile=$1
21+
for param in "${requiredParams[@]}"; do
22+
local paramValue=$(jq -r .$param < $paramsFile)
23+
if [ "null" == "$paramValue" ] || [ -z "$paramValue" ]; then
24+
echo "Parameter $param is required, exiting..."
25+
exit 1
26+
fi
27+
done
28+
}
29+
30+
function populateRequiredParams () {
31+
local paramsFile=$1
32+
printf "Checking required parameters... "
33+
checkRequiredParams $paramsFile
34+
# The jq command below sets environment variables based on the key-value pairs in a JSON-formatted file
35+
eval $(jq -r 'to_entries | .[] | "export \(.key)=\(.value)"' $paramsFile)
36+
printf "Done.\n"
37+
}
38+
39+
function set_variables() {
40+
printf "Setting environment variables...\n"
41+
SUBSCRIPTION_ID=${SUBSCRIPTION_ID:-""}
42+
RESOURCE_GROUP=${RESOURCE_GROUP:-""}
43+
LOCATION=${LOCATION:-""}
44+
AAD_CLIENT_ID=${AAD_CLIENT_ID:-""}
45+
AAD_OBJECT_ID=${AAD_OBJECT_ID:-""}
46+
AAD_TENANT_ID=${AAD_TENANT_ID:-""}
47+
AAD_TOKEN_ISSUER_URL=${AAD_TOKEN_ISSUER_URL:-"https://login.microsoftonline.com/$AAD_TENANT_ID/v2.0"}
48+
IMAGE_NAME=${IMAGE_NAME:-"graphrag:frontend"}
49+
REGISTRY_NAME=${REGISTRY_NAME:-"${RESOURCE_GROUP}reg"}
50+
APP_SERVICE_PLAN=${APP_SERVICE_PLAN:-"${RESOURCE_GROUP}-asp"}
51+
WEB_APP=${WEB_APP:-"${RESOURCE_GROUP}-playground"}
52+
WEB_APP_IDENTITY=${WEB_APP_IDENTITY:-"${WEB_APP}-identity"}
53+
#BACKEND_RESOURCE_GROUP=${BACKEND_RESOURCE_GROUP:-""} # needed for backend outbound vnet integration
54+
printf "Done setting environment variables.\n"
55+
}
56+
57+
function create_resource_group {
58+
printf "Setting subsctiption to $SUBSCRIPTION_ID and Creating resource group...\n"
59+
az account set --subscription $SUBSCRIPTION_ID > /dev/null
60+
az group create --name $RESOURCE_GROUP --location $LOCATION > /dev/null
61+
printf "Resource group created.\n"
62+
}
63+
64+
function create_acr() {
65+
printf "Creating Azure Container Registry...\n"
66+
az acr create --resource-group $RESOURCE_GROUP \
67+
--name $REGISTRY_NAME \
68+
--sku Basic \
69+
--admin-enabled true > /dev/null
70+
printf "Azure Container Registry created.\n"
71+
}
72+
73+
function build_and_push_image() {
74+
printf "Building and pushing image...\n"
75+
local SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
76+
az acr build --registry $REGISTRY_NAME -f $SCRIPT_DIR/../docker/Dockerfile-frontend --image $IMAGE_NAME $SCRIPT_DIR/../
77+
printf "Image built and pushed.\n"
78+
}
79+
80+
function create_app_service_plan() {
81+
printf "Creating app service plan...\n"
82+
az appservice plan create --name $APP_SERVICE_PLAN \
83+
--resource-group $RESOURCE_GROUP \
84+
--sku B3 \
85+
--is-linux > /dev/null
86+
printf "App service plan created.\n"
87+
}
88+
89+
function create_web_app() {
90+
printf "Creating web app...\n"
91+
az webapp create --resource-group $RESOURCE_GROUP \
92+
--plan $APP_SERVICE_PLAN \
93+
--name $WEB_APP \
94+
--deployment-container-image-name $REGISTRY_NAME.azurecr.io/$IMAGE_NAME > /dev/null
95+
printf "Web app created.\n"
96+
}
97+
98+
function create_web_app_identity() {
99+
printf "Creating web app identity...\n"
100+
IDENTITY_RESULT=$(az identity create --resource-group $RESOURCE_GROUP --name $WEB_APP_IDENTITY --output json)
101+
WEBAPP_IDENTITY_ID=$(jq -r .id <<< $IDENTITY_RESULT)
102+
WEBAPP_IDENTITY_OBJECT_ID=$(jq -r .principalId <<< $IDENTITY_RESULT)
103+
WEBAPP_IDENTITY_CLIENT_ID=$(jq -r .clientId <<< $IDENTITY_RESULT)
104+
az webapp identity assign --name $WEB_APP \
105+
--resource-group $RESOURCE_GROUP \
106+
--identities $WEBAPP_IDENTITY_ID > /dev/null
107+
printf "Web app identity created.\n"
108+
}
109+
function configure_registry_credentials() {
110+
printf "Configuring registry credentials...\n"
111+
ACR_ID=$(az acr show --name $REGISTRY_NAME --resource-group $RESOURCE_GROUP --query id --output tsv)
112+
az role assignment create --assignee $WEBAPP_IDENTITY_CLIENT_ID \
113+
--role AcrPull \
114+
--scope $ACR_ID > /dev/null
115+
az webapp config container set --name $WEB_APP \
116+
--resource-group $RESOURCE_GROUP \
117+
--container-image-name $REGISTRY_NAME.azurecr.io/$IMAGE_NAME > /dev/null
118+
printf "Registry credentials configured.\n"
119+
}
120+
121+
function configure_app_settings() {
122+
printf "Configuring app settings...\n"
123+
APP_SETTINGS=""
124+
while IFS='=' read -r name value
125+
do
126+
value="${value%\"}" # Remove opening quote
127+
value="${value#\"}" # Remove closing quote
128+
APP_SETTINGS="$APP_SETTINGS $name=$value"
129+
done < .env
130+
# echo $APP_SETTINGS
131+
az webapp config appsettings set --name $WEB_APP \
132+
--resource-group $RESOURCE_GROUP \
133+
--settings $APP_SETTINGS > /dev/null
134+
printf "App settings configured.\n"
135+
}
136+
137+
function create_federated_identity_credentials() {
138+
printf "Creating federated identity credentials...\n"
139+
EXISTING_CREDENTIAL_SUBJECTS=$(az rest --method GET --uri "https://graph.microsoft.com/beta/applications/$AAD_OBJECT_ID/federatedIdentityCredentials" -o json | jq -r '.value[].subject')
140+
if [[ "$EXISTING_CREDENTIAL_SUBJECTS" == *"$WEBAPP_IDENTITY_OBJECT_ID"* ]]; then
141+
echo "Federated identity credential already exists for the subject: $WEBAPP_IDENTITY_OBJECT_ID"
142+
else
143+
az webapp auth update \
144+
--name $WEB_APP \
145+
--resource-group $RESOURCE_GROUP \
146+
--enabled true \
147+
--action LoginWithAzureActiveDirectory \
148+
--aad-client-id $AAD_CLIENT_ID \
149+
--aad-token-issuer-url $AAD_TOKEN_ISSUER_URL > /dev/null
150+
az rest --method POST \
151+
--uri "https://graph.microsoft.com/beta/applications/$AAD_OBJECT_ID/federatedIdentityCredentials" \
152+
--body "{'name': '$WEB_APP', 'issuer': '$AAD_TOKEN_ISSUER_URL', 'subject': '$WEBAPP_IDENTITY_OBJECT_ID', 'audiences': [ 'api://AzureADTokenExchange' ]}" > /dev/null
153+
fi
154+
printf "Federated identity credentials created.\n"
155+
}
156+
157+
function configure_auth_settings() {
158+
printf "Configuring auth settings...\n"
159+
az webapp config appsettings set --resource-group $RESOURCE_GROUP \
160+
--name $WEB_APP \
161+
--slot-settings OVERRIDE_USE_MI_FIC_ASSERTION_CLIENTID=$WEBAPP_IDENTITY_CLIENT_ID \
162+
--verbose > /dev/null
163+
az webapp config appsettings list --resource-group $RESOURCE_GROUP \
164+
--name $WEB_APP > /dev/null
165+
166+
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)
167+
echo $authSettings > auth.json
168+
jq '.properties.identityProviders.azureActiveDirectory.registration.clientSecretSettingName = "OVERRIDE_USE_MI_FIC_ASSERTION_CLIENTID"' auth.json > tmp.json && mv tmp.json auth.json
169+
az rest --method PUT \
170+
--url "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Web/sites/$WEB_APP/config/authsettingsV2?api-version=2020-12-01" \
171+
--body @auth.json \
172+
--headers "Content-Type=application/json" > /dev/null
173+
rm auth.json
174+
printf "Auth settings configured.\n"
175+
}
176+
177+
function enable_https() {
178+
printf "Enabling HTTPS...\n"
179+
az webapp update --name $WEB_APP \
180+
--resource-group $RESOURCE_GROUP \
181+
--set httpsOnly=true > /dev/null
182+
printf "HTTPS enabled.\n"
183+
}
184+
185+
function update_appreg_redirect_uris() {
186+
printf "Updating app registration redirect URIs...\n"
187+
WEB_APP_URL=$(az webapp show --name $WEB_APP --resource-group $RESOURCE_GROUP --query defaultHostName --output tsv)
188+
NEW_REDIRECT_URI=https://$WEB_APP_URL/.auth/login/aad/callback
189+
# Fetch the current list of web redirect URIs
190+
CURRENT_URIS=$(az ad app show --id $AAD_CLIENT_ID --query "web.redirectUris" --output tsv)
191+
if ! echo "${CURRENT_URIS}" | grep -q "${NEW_REDIRECT_URI}"; then
192+
az ad app update --id $AAD_CLIENT_ID --web-redirect-uris ${CURRENT_URIS[@]} "$NEW_REDIRECT_URI" > /dev/null
193+
fi
194+
printf "App registration redirect URIs updated.\n"
195+
}
196+
197+
function restart_web_app() {
198+
printf "Restarting web app...\n"
199+
az webapp restart --name $WEB_APP --resource-group $RESOURCE_GROUP > /dev/null
200+
printf "Waiting for webapp to restart, webapp might take a few minutes to load.....\n"
201+
sleep 180
202+
printf "Web app restarted. \n"
203+
}
204+
205+
## The following function adds outbound vnet integration on the webapp so that the frontend container can access resources in the AKS cluster directly.
206+
## This will create a new subnet named "frontend" in the backend resource group's vnet if it does not exist.
207+
## 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.
208+
# function add_vnet_integration() {
209+
# VNET_NAME=$(az network vnet list --resource-group $BACKEND_RESOURCE_GROUP --query "[0].name" --output tsv)
210+
# VNET_ID=$(az network vnet list --resource-group $BACKEND_RESOURCE_GROUP --query "[0].id" --output tsv)
211+
# SUBNET_NAMES=$(az network vnet subnet list --resource-group $BACKEND_RESOURCE_GROUP --vnet-name $VNET_NAME --query "[].name" --output tsv)
212+
# SUBNET_NAME="frontend"
213+
# if [[ $SUBNET_NAMES == *$SUBNET_NAME* ]]; then
214+
# echo "Subnet with name $SUBNET_NAME already exists"
215+
# else
216+
# echo "Subnet with name $SUBNET_NAME does not exist, creating one now."
217+
# az network vnet subnet create --resource-group $BACKEND_RESOURCE_GROUP --vnet-name $VNET_NAME --name $SUBNET_NAME --address-prefixes 10.0.10.0/24
218+
# fi
219+
# az webapp vnet-integration add --name $WEB_APP --resource-group $RESOURCE_GROUP --vnet $VNET_ID --subnet $SUBNET_NAME
220+
# }
221+
222+
function usage() {
223+
echo
224+
echo "Usage: bash $0 [-h] -p <frontend_deploy.parameters.json>"
225+
echo "Description: Deployment script for the Frontend App for GraphRAG Solution Accelerator."
226+
echo "options:"
227+
echo " -h Print this help menu."
228+
echo " -p A JSON file containing the deployment parameters (frontend_deploy.parameters.json)."
229+
echo
230+
}
231+
232+
function main() {
233+
load_env_variables
234+
populateRequiredParams $PARAMS_FILE
235+
set_variables
236+
create_resource_group
237+
create_acr
238+
build_and_push_image
239+
create_app_service_plan
240+
create_web_app
241+
create_web_app_identity
242+
configure_registry_credentials
243+
configure_app_settings
244+
create_federated_identity_credentials
245+
configure_auth_settings
246+
enable_https
247+
# add_vnet_integration
248+
update_appreg_redirect_uris
249+
restart_web_app
250+
echo "**********Graphrag Frontend Web app deployment successful!**********"
251+
echo "Please visit the webapp at https://$WEB_APP_URL"
252+
echo "*******************************************************************"
253+
}
254+
255+
# print usage if no arguments are supplied
256+
[ $# -eq 0 ] && usage && exit 0
257+
PARAMS_FILE=""
258+
while getopts ":p:h" option; do
259+
case "${option}" in
260+
p)
261+
PARAMS_FILE=${OPTARG}
262+
;;
263+
h | *)
264+
usage
265+
exit 0
266+
;;
267+
esac
268+
done
269+
shift $((OPTIND-1))
270+
# check if required arguments are supplied
271+
if [ ! -f $PARAMS_FILE ]; then
272+
echo "Error: invalid required argument."
273+
usage
274+
exit 1
275+
fi
276+
277+
main
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"SUBSCRIPTION_ID": "<your_subscription_id>",
3+
"RESOURCE_GROUP": "<your_resource_group>",
4+
"LOCATION": "<your_location>",
5+
"AAD_CLIENT_ID": "<your_aad_client_id>",
6+
"AAD_OBJECT_ID": "<your_aad_object_id>",
7+
"AAD_TENANT_ID": "<your_aad_tenant_id>"
8+
}

0 commit comments

Comments
 (0)