mirror of
https://github.com/Azure-Samples/graphrag-accelerator.git
synced 2025-06-27 04:39:57 +00:00
644 lines
28 KiB
Bash
Executable File
644 lines
28 KiB
Bash
Executable File
# Copyright (c) Microsoft Corporation.
|
|
# Licensed under the MIT License.
|
|
#!/usr/bin/env bash
|
|
|
|
#set -x # uncomment this line to debug
|
|
|
|
aksNamespace="graphrag"
|
|
|
|
# OPTIONAL PARAMS
|
|
AISEARCH_AUDIENCE=""
|
|
AISEARCH_ENDPOINT_SUFFIX=""
|
|
APIM_NAME=""
|
|
RESOURCE_BASE_NAME=""
|
|
REPORTERS=""
|
|
GRAPHRAG_COGNITIVE_SERVICES_ENDPOINT=""
|
|
CONTAINER_REGISTRY_SERVER=""
|
|
|
|
requiredParams=(
|
|
LOCATION
|
|
GRAPHRAG_API_BASE
|
|
GRAPHRAG_API_VERSION
|
|
GRAPHRAG_LLM_MODEL
|
|
GRAPHRAG_LLM_DEPLOYMENT_NAME
|
|
GRAPHRAG_EMBEDDING_MODEL
|
|
GRAPHRAG_EMBEDDING_DEPLOYMENT_NAME
|
|
RESOURCE_GROUP
|
|
)
|
|
|
|
# Note, setting a command result to a local variable will mark $? as successful. Use a global variable.
|
|
exitIfCommandFailed () {
|
|
local res=$1
|
|
local msg=$2
|
|
if [ 0 -ne $res ]; then
|
|
printf "$msg\n"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
exitIfValueEmpty () {
|
|
local value=$1
|
|
local msg=$2
|
|
if [ -z "$value" ]; then
|
|
printf "$msg\n"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
versionCheck () {
|
|
# assume the version is in the format major.minor.patch
|
|
local TOOL=$1
|
|
local VERSION=$2
|
|
local MINIMUM_VERSION_REQUIREMENT=$3
|
|
for i in 1 2 3; do
|
|
part1=$(echo $VERSION | cut -d "." -f $i)
|
|
part2=$(echo $MINIMUM_VERSION_REQUIREMENT | cut -d "." -f $i)
|
|
if [ $part1 -gt $part2 ]; then
|
|
return 0
|
|
fi
|
|
if [ $part1 -lt $part2 ]; then
|
|
echo "$TOOL version requirement >= $MINIMUM_VERSION_REQUIREMENT, but you have version $VERSION"
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
checkRequiredTools () {
|
|
printf "Checking for required tools... "
|
|
|
|
which sed > /dev/null
|
|
exitIfCommandFailed $? "sed is required, exiting..."
|
|
|
|
which kubectl > /dev/null
|
|
exitIfCommandFailed $? "kubectl is required, exiting..."
|
|
|
|
which helm > /dev/null
|
|
exitIfCommandFailed $? "helm is required, exiting..."
|
|
|
|
which jq > /dev/null
|
|
exitIfCommandFailed $? "jq is required, exiting..."
|
|
|
|
which yq > /dev/null
|
|
exitIfCommandFailed $? "yq is required, exiting..."
|
|
|
|
which az > /dev/null
|
|
exitIfCommandFailed $? "azcli is required, exiting..."
|
|
|
|
which curl > /dev/null
|
|
exitIfCommandFailed $? "curl is required, exiting..."
|
|
|
|
# minimum version check for jq, yq, and az cli
|
|
local JQ_VERSION=$(jq --version | cut -d'-' -f2)
|
|
local major minor patch
|
|
IFS='.' read -r major minor patch <<< "$JQ_VERSION"
|
|
if [ -z $patch ]; then
|
|
# NOTE: older acceptable versions of jq report a version number without the patch number.
|
|
# if patch version is not present, set it to 0
|
|
patch=0
|
|
JQ_VERSION="$major.$minor.$patch"
|
|
fi
|
|
local YQ_VERSION=`yq --version | awk '{print substr($4,2)}'`
|
|
local AZ_VERSION=`az version -o json | jq -r '.["azure-cli"]'`
|
|
versionCheck "jq" $JQ_VERSION "1.6.0"
|
|
versionCheck "yq" $YQ_VERSION "4.40.7"
|
|
versionCheck "az cli" $AZ_VERSION "2.55.0"
|
|
printf "Done.\n"
|
|
}
|
|
|
|
checkRequiredParams () {
|
|
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
|
|
}
|
|
|
|
populateRequiredParams () {
|
|
local paramsFile=$1
|
|
printf "Checking required parameters... "
|
|
checkRequiredParams $paramsFile
|
|
# The jq command below sets environment variable based on the key-value pairs in a JSON-formatted file
|
|
eval $(jq -r 'to_entries | .[] | "export \(.key)=\(.value)"' $paramsFile)
|
|
printf "Done.\n"
|
|
}
|
|
|
|
populateOptionalParams () {
|
|
# a list of optional environment variables that could be set in the params file.
|
|
# using the default values below is recommended.
|
|
local paramsFile=$1
|
|
echo "Checking optional parameters..."
|
|
value=$(jq -r .APIM_NAME < $paramsFile)
|
|
if [ "null" != "$value" ]; then
|
|
APIM_NAME="$value"
|
|
printf "\setting tAPIM_NAME=$APIM_NAME\n"
|
|
fi
|
|
if [ -z "$AISEARCH_ENDPOINT_SUFFIX" ]; then
|
|
AISEARCH_ENDPOINT_SUFFIX="search.windows.net"
|
|
printf "\tsetting AISEARCH_ENDPOINT_SUFFIX=$AISEARCH_ENDPOINT_SUFFIX\n"
|
|
fi
|
|
if [ -z "$AISEARCH_AUDIENCE" ]; then
|
|
AISEARCH_AUDIENCE="https://search.azure.com"
|
|
printf "\tsetting AISEARCH_AUDIENCE=$AISEARCH_AUDIENCE\n"
|
|
fi
|
|
if [ -z "$PUBLISHER_NAME" ]; then
|
|
PUBLISHER_NAME="publisher"
|
|
printf "\tsetting PUBLISHER_NAME=$PUBLISHER_NAME\n"
|
|
fi
|
|
if [ -z "$PUBLISHER_EMAIL" ]; then
|
|
PUBLISHER_EMAIL="publisher@microsoft.com"
|
|
printf "\tsetting PUBLISHER_EMAIL=$PUBLISHER_EMAIL\n"
|
|
fi
|
|
if [ -z "$CONTAINER_REGISTRY_EMAIL" ]; then
|
|
CONTAINER_REGISTRY_EMAIL="publisher@microsoft.com"
|
|
printf "\tsetting CONTAINER_REGISTRY_EMAIL=$CONTAINER_REGISTRY_EMAIL\n"
|
|
fi
|
|
if [ -z "$CLOUD_NAME" ]; then
|
|
CLOUD_NAME="AzurePublicCloud"
|
|
printf "\tsetting CLOUD_NAME=$CLOUD_NAME\n"
|
|
fi
|
|
if [ ! -z "$RESOURCE_BASE_NAME" ]; then
|
|
printf "\tsetting RESOURCE_BASE_NAME=$RESOURCE_BASE_NAME\n"
|
|
fi
|
|
if [ -z "$REPORTERS" ]; then
|
|
REPORTERS="blob,console,app_insights"
|
|
printf "\tsetting REPORTERS=blob,console,app_insights\n"
|
|
fi
|
|
if [ -z "$GRAPHRAG_COGNITIVE_SERVICES_ENDPOINT" ]; then
|
|
GRAPHRAG_COGNITIVE_SERVICES_ENDPOINT="https://cognitiveservices.azure.com/.default"
|
|
printf "\tsetting GRAPHRAG_COGNITIVE_SERVICES_ENDPOINT=$GRAPHRAG_COGNITIVE_SERVICES_ENDPOINT\n"
|
|
fi
|
|
if [ -z "$GRAPHRAG_IMAGE" ]; then
|
|
GRAPHRAG_IMAGE="graphrag:backend"
|
|
printf "\tsetting GRAPHRAG_IMAGE=$GRAPHRAG_IMAGE\n"
|
|
fi
|
|
printf "Done.\n"
|
|
}
|
|
|
|
populateParams () {
|
|
populateRequiredParams $1
|
|
populateOptionalParams $1
|
|
}
|
|
|
|
createResourceGroupIfNotExists () {
|
|
local location=$1
|
|
local rg=$2
|
|
printf "Checking if resource group $rg exists... "
|
|
az group show -n $rg -o json >/dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
printf "No.\n"
|
|
printf "Creating resource group... "
|
|
az group create -l $location -n $rg >/dev/null 2>&1
|
|
printf "Done.\n"
|
|
else
|
|
printf "Yes.\n"
|
|
fi
|
|
}
|
|
|
|
createSshkeyIfNotExists () {
|
|
local rg=$1
|
|
local keyName="aks-publickey"
|
|
printf "Checking if sshkey exists... "
|
|
local keyDetails=$(az sshkey show -g $rg --name $keyName -o json 2> /dev/null)
|
|
if [ -z "$keyDetails" ]; then
|
|
printf "No.\n"
|
|
printf "Creating sshkey... "
|
|
local keyDetails=$(az sshkey create -g $rg --name $keyName -o json)
|
|
exitIfCommandFailed $? "Error creating sshkey."
|
|
# TODO Upload private key to keyvault
|
|
else
|
|
printf "Yes.\n"
|
|
fi
|
|
SSHKEY_DETAILS=$keyDetails
|
|
}
|
|
|
|
setupAksCredentials () {
|
|
local rg=$1
|
|
local aks=$2
|
|
printf "Getting AKS credentials... "
|
|
tempResult=$(az aks get-credentials -g $rg -n $aks --overwrite-existing 2>&1)
|
|
exitIfCommandFailed $? "Error getting AKS credentials, exiting...\n$tempResult"
|
|
kubectl config set-context $aks --namespace=$aksNamespace
|
|
printf "Done\n"
|
|
}
|
|
|
|
populateAksVnetInfo () {
|
|
local rg=$1
|
|
local aks=$2
|
|
printf "Retrieving AKS VNet info... "
|
|
local aksDetails=$(AZURE_CLIENTS_SHOW_SECRETS_WARNING=False az aks show -g $rg -n $aks -o json)
|
|
AKS_MANAGED_RG=$(jq -r .networkProfile.loadBalancerProfile.effectiveOutboundIPs[0].resourceGroup <<< $aksDetails)
|
|
AKS_VNET_NAME=$(az network vnet list -g $AKS_MANAGED_RG -o json | jq -r .[0].name)
|
|
AKS_VNET_ID=$(az network vnet list -g $AKS_MANAGED_RG -o json | jq -r .[0].id)
|
|
exitIfValueEmpty "$AKS_MANAGED_RG" "Unable to populate AKS managed resource group name, exiting..."
|
|
exitIfValueEmpty "$AKS_VNET_NAME" "Unable to populate AKS vnet name, exiting..."
|
|
exitIfValueEmpty "$AKS_VNET_ID" "Unable to populate AKS vnet resource id, exiting..."
|
|
printf "Done\n"
|
|
}
|
|
|
|
deployAzureResources () {
|
|
echo "Deploying Azure resources..."
|
|
SSH_PUBLICKEY=$(jq -r .publicKey <<< $SSHKEY_DETAILS)
|
|
exitIfValueEmpty "$SSH_PUBLICKEY" "Unable to read ssh publickey, exiting..."
|
|
datetime="`date +%Y-%m-%d_%H-%M-%S`"
|
|
deploy_name="graphrag-deploy-$datetime"
|
|
echo "Deployment name: $deploy_name"
|
|
AZURE_DEPLOY_VALIDATION=$(az deployment group what-if --no-pretty-print --only-show-errors --name "$deploy_name" --resource-group $RESOURCE_GROUP --no-prompt -o json --template-file ./main.bicep \
|
|
--parameters "resourceBaseName=$RESOURCE_BASE_NAME" \
|
|
--parameters "graphRagName=$RESOURCE_GROUP" \
|
|
--parameters "apimName=$APIM_NAME" \
|
|
--parameters "publisherName=$PUBLISHER_NAME" \
|
|
--parameters "aksSshRsaPublicKey=$SSH_PUBLICKEY" \
|
|
--parameters "publisherEmail=$PUBLISHER_EMAIL" \
|
|
--parameters "enablePrivateEndpoints=$ENABLE_PRIVATE_ENDPOINTS")
|
|
exitIfCommandFailed $? "Error validating Azure resource deployment..."
|
|
echo "Validate Deployment Results"
|
|
echo "$AZURE_DEPLOY_VALIDATION" | jq .
|
|
exit 0
|
|
AZURE_DEPLOY_RESULTS=$(az deployment group create --name "$deploy_name" --resource-group $RESOURCE_GROUP --no-prompt -o json --template-file ./main.bicep \
|
|
--parameters "resourceBaseName=$RESOURCE_BASE_NAME" \
|
|
--parameters "graphRagName=$RESOURCE_GROUP" \
|
|
--parameters "apimName=$APIM_NAME" \
|
|
--parameters "publisherName=$PUBLISHER_NAME" \
|
|
--parameters "aksSshRsaPublicKey=$SSH_PUBLICKEY" \
|
|
--parameters "publisherEmail=$PUBLISHER_EMAIL" \
|
|
--parameters "enablePrivateEndpoints=$ENABLE_PRIVATE_ENDPOINTS")
|
|
exitIfCommandFailed $? "Error deploying Azure resources..."
|
|
AZURE_OUTPUTS=$(jq -r .properties.outputs <<< $AZURE_DEPLOY_RESULTS)
|
|
exitIfCommandFailed $? "Error parsing outputs from Azure resource deployment..."
|
|
}
|
|
|
|
assignAOAIRoleToManagedIdentity() {
|
|
echo "Assigning 'Cognitive Services OpenAI Contributor' AOAI role to managed identity..."
|
|
local servicePrincipalId=$(jq -r .azure_workload_identity_principal_id.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$servicePrincipalId" "Unable to parse service principal id from azure outputs, exiting..."
|
|
local scope=$(az cognitiveservices account list --query "[?contains(properties.endpoint, '$GRAPHRAG_API_BASE')] | [0].id" -o json)
|
|
scope=$(jq -r <<< $scope) # strip out quotes
|
|
az role assignment create --role "Cognitive Services OpenAI Contributor" --assignee "$servicePrincipalId" --scope "$scope" > /dev/null 2>&1
|
|
exitIfCommandFailed $? "Error assigning role to service principal, exiting..."
|
|
}
|
|
|
|
assignAKSPullRoleToRegistry() {
|
|
echo "Assigning 'ACRPull' role to AKS to access container registry..."
|
|
local rg=$1
|
|
local aks=$2
|
|
local registry=$3
|
|
local registry_id=$(az acr show --name $registry --query id -o json)
|
|
registry_id=$(jq -r <<< $registry_id) # strip out quotes
|
|
exitIfValueEmpty "$registry_id" "Unable to retrieve container registry id, exiting..."
|
|
az aks update --name $aks --resource-group $rg --attach-acr $registry_id -o json > /dev/null 2>&1
|
|
exitIfCommandFailed $? "Error assigning AKS pull role to container registry, exiting..."
|
|
}
|
|
|
|
peerVirtualNetworks () {
|
|
echo "Peering APIM VNet to AKS..."
|
|
local apimVnetName=$(jq -r .azure_apim_vnet_name.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$apimVnetName" "Unable to parse apim vnet name from deployment outputs, exiting..."
|
|
datetime="`date +%Y-%m-%d_%H-%M-%S`"
|
|
AZURE_DEPLOY_RESULTS=$(az deployment group create --name "vnet-apim-to-aks-$datetime" --no-prompt -o json --template-file ./core/vnet/vnet-peering.bicep \
|
|
-g $RESOURCE_GROUP \
|
|
--parameters "name=aks" \
|
|
--parameters "vnetName=$apimVnetName" \
|
|
--parameters "remoteVnetId=$AKS_VNET_ID")
|
|
exitIfCommandFailed $? "Error peering apim vnet to aks..."
|
|
|
|
echo "Peering AKS VNet to APIM..."
|
|
local apimVnetId=$(jq -r .azure_apim_vnet_id.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$apimVnetId" "Unable to parse apim vnet resource id from deployment outputs, exiting..."
|
|
datetime="`date +%Y-%m-%d_%H-%M-%S`"
|
|
AZURE_DEPLOY_RESULTS=$(az deployment group create --name "vnet-aks-to-apim-$datetime" --no-prompt -o json --template-file ./core/vnet/vnet-peering.bicep \
|
|
-g $AKS_MANAGED_RG \
|
|
--parameters "name=apim" \
|
|
--parameters "vnetName=$AKS_VNET_NAME" \
|
|
--parameters "remoteVnetId=$apimVnetId")
|
|
exitIfCommandFailed $? "Error peering aks vnet to apim..."
|
|
echo "...peering complete"
|
|
}
|
|
|
|
linkPrivateDnsToAks () {
|
|
echo "Linking private DNS zone to AKS..."
|
|
local privateDnsZoneNames=$(jq -r .azure_private_dns_zones.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$privateDnsZoneNames" "Unable to parse private DNS zone names from deployment outputs, exiting..."
|
|
AZURE_DEPLOY_RESULTS=$(az deployment group create --name "private-dns-to-aks" --no-prompt -o json --template-file ./core/vnet/batch-private-dns-vnet-link.bicep \
|
|
-g $RESOURCE_GROUP \
|
|
--parameters "vnetResourceIds=[\"$AKS_VNET_ID\"]" \
|
|
--parameters "privateDnsZoneNames=$privateDnsZoneNames")
|
|
exitIfCommandFailed $? "Error linking private DNS to AKS vnet..."
|
|
echo "...linking private DNS complete"
|
|
}
|
|
|
|
installGraphRAGHelmChart () {
|
|
printf "Deploying graphrag helm chart... "
|
|
local workloadId=$(jq -r .azure_workload_identity_client_id.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$workloadId" "Unable to parse workload id from Azure outputs, exiting..."
|
|
|
|
local serviceAccountName=$(jq -r .azure_aks_service_account_name.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$serviceAccountName" "Unable to parse service account name from Azure outputs, exiting..."
|
|
|
|
local appInsightsConnectionString=$(jq -r .azure_app_insights_connection_string.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$appInsightsConnectionString" "Unable to parse app insights connection string from Azure outputs, exiting..."
|
|
|
|
local aiSearchName=$(jq -r .azure_ai_search_name.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$aiSearchName" "Unable to parse AI search name from Azure outputs, exiting..."
|
|
|
|
local cosmosEndpoint=$(jq -r .azure_cosmosdb_endpoint.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$cosmosEndpoint" "Unable to parse CosmosDB endpoint from Azure outputs, exiting..."
|
|
echo "cosmos endpoint: $cosmosEndpoint"
|
|
|
|
local graphragHostname=$(jq -r .azure_graphrag_hostname.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$graphragHostname" "Unable to parse graphrag hostname from deployment outputs, exiting..."
|
|
|
|
local storageAccountBlobUrl=$(jq -r .azure_storage_account_blob_url.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$storageAccountBlobUrl" "Unable to parse storage account blob url from deployment outputs, exiting..."
|
|
echo "storage account url: $storageAccountBlobUrl"
|
|
|
|
local graphragImageName=$(sed -rn "s/([^:]+).*/\1/p" <<< "$GRAPHRAG_IMAGE")
|
|
local graphragImageVersion=$(sed -rn "s/[^:]+:(.*)/\1/p" <<< "$GRAPHRAG_IMAGE")
|
|
exitIfValueEmpty "$graphragImageName" "Unable to parse graphrag image name, exiting..."
|
|
exitIfValueEmpty "$graphragImageVersion" "Unable to parse graphrag image version, exiting..."
|
|
|
|
helm dependency update ./helm/graphrag
|
|
exitIfCommandFailed $? "Error updating helm dependencies, exiting..."
|
|
# Some platforms require manually adding helm repositories to the local helm registry
|
|
# This is a workaround for the issue where the helm chart is not able to add the repository itself
|
|
yq '.dependencies | map(["helm", "repo", "add", .name, .repository] | join(" "))' ./helm/graphrag/Chart.yaml | sed 's/^..//' | sh --;
|
|
helm dependency build ./helm/graphrag --namespace $aksNamespace
|
|
exitIfCommandFailed $? "Error building helm dependencies, exiting..."
|
|
local escapedReporters=$(sed "s/,/\\\,/g" <<< "$REPORTERS")
|
|
reset_x=true
|
|
if ! [ -o xtrace ]; then
|
|
set -x
|
|
else
|
|
reset_x=false
|
|
fi
|
|
# Your script logic goes here
|
|
helm upgrade -i graphrag ./helm/graphrag -f ./helm/graphrag/values.yaml --namespace $aksNamespace --create-namespace \
|
|
--set "serviceAccount.name=$serviceAccountName" \
|
|
--set "serviceAccount.annotations.azure\.workload\.identity/client-id=$workloadId" \
|
|
--set "index.image.repository=$CONTAINER_REGISTRY_SERVER/$graphragImageName" \
|
|
--set "index.image.tag=$graphragImageVersion" \
|
|
--set "query.image.repository=$CONTAINER_REGISTRY_SERVER/$graphragImageName" \
|
|
--set "query.image.tag=$graphragImageVersion" \
|
|
--set "ingress.host=$graphragHostname" \
|
|
--set "graphragConfig.APP_INSIGHTS_CONNECTION_STRING=$appInsightsConnectionString" \
|
|
--set "graphragConfig.AI_SEARCH_URL=https://$aiSearchName.$AISEARCH_ENDPOINT_SUFFIX" \
|
|
--set "graphragConfig.AI_SEARCH_AUDIENCE=$AISEARCH_AUDIENCE" \
|
|
--set "graphragConfig.COSMOS_URI_ENDPOINT=$cosmosEndpoint" \
|
|
--set "graphragConfig.DEBUG_MODE=$DEBUG_MODE" \
|
|
--set "graphragConfig.GRAPHRAG_API_BASE=$GRAPHRAG_API_BASE" \
|
|
--set "graphragConfig.GRAPHRAG_API_VERSION=$GRAPHRAG_API_VERSION" \
|
|
--set "graphragConfig.GRAPHRAG_COGNITIVE_SERVICES_ENDPOINT=$GRAPHRAG_COGNITIVE_SERVICES_ENDPOINT" \
|
|
--set "graphragConfig.GRAPHRAG_LLM_MODEL=$GRAPHRAG_LLM_MODEL" \
|
|
--set "graphragConfig.GRAPHRAG_LLM_DEPLOYMENT_NAME=$GRAPHRAG_LLM_DEPLOYMENT_NAME" \
|
|
--set "graphragConfig.GRAPHRAG_EMBEDDING_MODEL=$GRAPHRAG_EMBEDDING_MODEL" \
|
|
--set "graphragConfig.GRAPHRAG_EMBEDDING_DEPLOYMENT_NAME=$GRAPHRAG_EMBEDDING_DEPLOYMENT_NAME" \
|
|
--set "graphragConfig.REPORTERS=$escapedReporters" \
|
|
--set "graphragConfig.STORAGE_ACCOUNT_BLOB_URL=$storageAccountBlobUrl"
|
|
|
|
local helmResult=$?
|
|
"$reset_x" && set +x
|
|
exitIfCommandFailed $helmResult "Error deploying helm chart, exiting..."
|
|
}
|
|
|
|
waitForGraphragExternalIp () {
|
|
local -i maxTries=14
|
|
local available="false"
|
|
printf "Checking for GraphRAG external IP"
|
|
for ((i=0;i < $maxTries; i++)); do
|
|
TMP_GRAPHRAG_SERVICE_IP=$(kubectl get ingress --namespace $aksNamespace graphrag --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}" 2> /dev/null)
|
|
if [ $? -eq 0 ]; then
|
|
available="true"
|
|
GRAPHRAG_SERVICE_IP=$TMP_GRAPHRAG_SERVICE_IP
|
|
break
|
|
fi
|
|
sleep 10
|
|
printf "."
|
|
done
|
|
if [ $available == "true" ]; then
|
|
printf " Available.\n"
|
|
else
|
|
printf " Failed.\n"
|
|
fi
|
|
}
|
|
|
|
waitForGraphrag () {
|
|
local backendSwaggerUrl=$1
|
|
local -i maxTries=20
|
|
local available="false"
|
|
printf "Checking for GraphRAG availability"
|
|
for ((i=0;i < $maxTries; i++)); do
|
|
az rest --method get --url $backendSwaggerUrl > /dev/null 2>&1
|
|
if [ $? -eq 0 ]; then
|
|
available="true"
|
|
break
|
|
fi
|
|
sleep 20
|
|
printf "."
|
|
done
|
|
if [ $available == "true" ]; then
|
|
printf " Available.\n"
|
|
else
|
|
printf " Failed.\n"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
deployGraphragDnsRecord () {
|
|
waitForGraphragExternalIp
|
|
exitIfValueEmpty "$GRAPHRAG_SERVICE_IP" "Unable to get GraphRAG external IP."
|
|
|
|
local dnsZoneName=$(jq -r .azure_dns_zone_name.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$dnsZoneName" "Error parsing DNS zone name from azure outputs, exiting..."
|
|
AZURE_GRAPHRAG_DNS_DEPLOY_RESULT=$(az deployment group create -g $RESOURCE_GROUP --name graphrag-dns --template-file core/vnet/private-dns-zone-a-record.bicep --no-prompt \
|
|
--parameters "name=graphrag" \
|
|
--parameters "dnsZoneName=$dnsZoneName" \
|
|
--parameters "ipv4Address=$GRAPHRAG_SERVICE_IP")
|
|
exitIfCommandFailed $? "Error creating GraphRAG DNS record, exiting..."
|
|
}
|
|
|
|
deployGraphragAPI () {
|
|
echo "Registering GraphRAG API with APIM..."
|
|
local apimGatewayUrl=$(jq -r .azure_apim_url.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$apimGatewayUrl" "Unable to parse APIM gateway url from Azure outputs, exiting..."
|
|
local apimName=$(jq -r .azure_apim_name.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$apimName" "Error parsing apim name from azure outputs, exiting..."
|
|
local backendSwaggerUrl="$apimGatewayUrl/manpage/openapi.json"
|
|
local graphragUrl=$(jq -r .azure_graphrag_url.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$graphragUrl" "Error parsing GraphRAG URL from azure outputs, exiting..."
|
|
|
|
waitForGraphrag $backendSwaggerUrl
|
|
|
|
# download the openapi spec from the backend and import it into APIM
|
|
az rest --method get --url $backendSwaggerUrl -o json > core/apim/graphrag-openapi.json 2>/dev/null
|
|
AZURE_GRAPHRAG_API_RESULT=$(az deployment group create --resource-group $RESOURCE_GROUP --name graphrag-api --template-file core/apim/apim.graphrag-servicedef.bicep --no-prompt \
|
|
--parameters "backendUrl=$graphragUrl" \
|
|
--parameters "name=GraphRAG" \
|
|
--parameters "apimname=$apimName")
|
|
exitIfCommandFailed $? "Error registering graphrag API, exiting..."
|
|
# cleanup
|
|
rm core/apim/graphrag-openapi.json
|
|
}
|
|
|
|
grantDevAccessToAzureResources() {
|
|
# This function is used to grant the deployer of this script "developer" access to GraphRAG Azure resources
|
|
# by assigning the necessary RBAC roles for Azure Storage, AI Search, and CosmosDB to the signed-in user.
|
|
# This will grant the deployer access to the storage account, cosmos db, and AI search services in the resource group via the Azure portal.
|
|
echo "Granting deployer developer access to Azure resources..."
|
|
|
|
# get subscription id of the active subscription
|
|
local azureAccount=$(az account show -o json)
|
|
local subscriptionId=$(jq -r .id <<< $azureAccount)
|
|
exitIfValueEmpty $subscriptionId "Subscription ID not found"
|
|
|
|
# get principal/object id of the signed in user
|
|
local azureUserDetails=$(az ad signed-in-user show -o json)
|
|
local principalId=$(jq -r .id <<< $azureUserDetails)
|
|
exitIfValueEmpty $principalId "Principal ID not found"
|
|
|
|
# assign storage account roles
|
|
local storageAccountDetails=$(az storage account list --resource-group $RESOURCE_GROUP -o json)
|
|
local storageAccountName=$(jq -r .[0].name <<< $storageAccountDetails)
|
|
exitIfValueEmpty $storageAccountName "Storage account not found"
|
|
az role assignment create --role "Storage Blob Data Contributor" --assignee $principalId --scope "/subscriptions/$subscriptionId/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Storage/storageAccounts/$storageAccountName" > /dev/null
|
|
az role assignment create --role "Storage Queue Data Contributor" --assignee $principalId --scope "/subscriptions/$subscriptionId/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Storage/storageAccounts/$storageAccountName" > /dev/null
|
|
|
|
# assign cosmos db role
|
|
local cosmosDbDetails=$(az cosmosdb list --resource-group $RESOURCE_GROUP -o json)
|
|
local cosmosDbName=$(jq -r .[0].name <<< $cosmosDbDetails)
|
|
exitIfValueEmpty $cosmosDbName "CosmosDB account not found"
|
|
az cosmosdb sql role assignment create --account-name $cosmosDbName --resource-group $RESOURCE_GROUP --scope "/" --principal-id $principalId --role-definition-id /subscriptions/$subscriptionId/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.DocumentDB/databaseAccounts/graphrag/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002 > /dev/null
|
|
|
|
# assign AI search roles
|
|
local searchServiceDetails=$(az search service list --resource-group $RESOURCE_GROUP -o json)
|
|
local searchServiceName=$(jq -r .[0].name <<< $searchServiceDetails)
|
|
exitIfValueEmpty $searchServiceName "AI Search service not found"
|
|
az role assignment create --role "Contributor" --assignee $principalId --scope "/subscriptions/$subscriptionId/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Search/searchServices/$searchServiceName" > /dev/null
|
|
az role assignment create --role "Search Index Data Contributor" --assignee $principalId --scope "/subscriptions/$subscriptionId/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Search/searchServices/$searchServiceName" > /dev/null
|
|
az role assignment create --role "Search Index Data Reader" --assignee $principalId --scope "/subscriptions/$subscriptionId/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Search/searchServices/$searchServiceName" > /dev/null
|
|
}
|
|
|
|
createAcrIfNotExists() {
|
|
# check if container registry exists
|
|
if [ ! -z "$CONTAINER_REGISTRY_SERVER" ]; then
|
|
printf "Checking if container registry '$CONTAINER_REGISTRY_SERVER' exists... "
|
|
az acr show --name $CONTAINER_REGISTRY_SERVER > /dev/null 2>&1
|
|
exitIfCommandFailed $? "Container registry '$CONTAINER_REGISTRY_SERVER' not found, exiting..."
|
|
printf "Yes.\n"
|
|
return 0
|
|
fi
|
|
# else deploy a new container registry
|
|
printf "Creating container registry... "
|
|
AZURE_ACR_DEPLOY_RESULT=$(az deployment group create --resource-group $RESOURCE_GROUP --name "acr-deployment" --template-file core/acr/acr.bicep --only-show-errors --no-prompt -o json \
|
|
--parameters "name=$CONTAINER_REGISTRY_SERVER")
|
|
exitIfCommandFailed $? "Error creating container registry, exiting..."
|
|
CONTAINER_REGISTRY_SERVER=$(jq -r .properties.outputs.loginServer.value <<< $AZURE_ACR_DEPLOY_RESULT)
|
|
exitIfValueEmpty "$CONTAINER_REGISTRY_SERVER" "Unable to parse container registry login server from deployment, exiting..."
|
|
printf "container registry '$CONTAINER_REGISTRY_SERVER' created.\n"
|
|
}
|
|
|
|
deployDockerImageToACR() {
|
|
printf "Deploying docker image '${GRAPHRAG_IMAGE}' to container registry '${CONTAINER_REGISTRY_SERVER}'..."
|
|
local SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";
|
|
az acr build --registry $CONTAINER_REGISTRY_SERVER -f $SCRIPT_DIR/../docker/Dockerfile-backend --image $GRAPHRAG_IMAGE $SCRIPT_DIR/../ > /dev/null 2>&1
|
|
exitIfCommandFailed $? "Error deploying docker image, exiting..."
|
|
printf " Done.\n"
|
|
}
|
|
|
|
################################################################################
|
|
# Help menu #
|
|
################################################################################
|
|
usage() {
|
|
echo
|
|
echo "Usage: bash $0 [-h|d|g] -p <deploy.parameters.json>"
|
|
echo "Description: Deployment script for the GraphRAG Solution Accelerator."
|
|
echo "options:"
|
|
echo " -h Print this help menu."
|
|
echo " -d Disable private endpoint usage."
|
|
echo " -g Developer use only. Grants deployer of this script access to Azure Storage, AI Search, and CosmosDB. Will disable private endpoints (-d) and enable debug mode."
|
|
echo " -p A JSON file containing the deployment parameters (deploy.parameters.json)."
|
|
echo
|
|
}
|
|
# print usage if no arguments are supplied
|
|
[ $# -eq 0 ] && usage && exit 0
|
|
# parse arguments
|
|
ENABLE_PRIVATE_ENDPOINTS=true
|
|
DEBUG_MODE=off
|
|
GRANT_DEV_ACCESS=0 # false
|
|
PARAMS_FILE=""
|
|
while getopts ":dgp:h" option; do
|
|
case "${option}" in
|
|
d)
|
|
ENABLE_PRIVATE_ENDPOINTS=false
|
|
;;
|
|
g)
|
|
ENABLE_PRIVATE_ENDPOINTS=false
|
|
GRANT_DEV_ACCESS=1 # true
|
|
DEBUG_MODE=on
|
|
;;
|
|
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 Program #
|
|
################################################################################
|
|
checkRequiredTools
|
|
|
|
populateParams $PARAMS_FILE
|
|
|
|
# Create resource group
|
|
createResourceGroupIfNotExists $LOCATION $RESOURCE_GROUP
|
|
|
|
# Create azure container registry if it does not exist
|
|
createAcrIfNotExists
|
|
|
|
# Deploy the graphrag backend docker image to ACR
|
|
deployDockerImageToACR
|
|
|
|
# Generate ssh key for AKS
|
|
createSshkeyIfNotExists $RESOURCE_GROUP
|
|
|
|
# Deploy Azure resources
|
|
deployAzureResources
|
|
|
|
# Setup RBAC roles to access an already deployed Azure OpenAI service.
|
|
AKS_NAME=$(jq -r .azure_aks_name.value <<< $AZURE_OUTPUTS)
|
|
exitIfValueEmpty "$AKS_NAME" "Unable to parse AKS name from azure deployment outputs, exiting..."
|
|
assignAOAIRoleToManagedIdentity
|
|
assignAKSPullRoleToRegistry $RESOURCE_GROUP $AKS_NAME $CONTAINER_REGISTRY_SERVER
|
|
|
|
# Deploy kubernetes resources
|
|
setupAksCredentials $RESOURCE_GROUP $AKS_NAME
|
|
populateAksVnetInfo $RESOURCE_GROUP $AKS_NAME
|
|
if [ "$ENABLE_PRIVATE_ENDPOINTS" = "true" ]; then
|
|
linkPrivateDnsToAks
|
|
fi
|
|
peerVirtualNetworks
|
|
|
|
# Install GraphRAG helm chart
|
|
installGraphRAGHelmChart
|
|
|
|
# Import and setup GraphRAG API in APIM
|
|
deployGraphragDnsRecord
|
|
deployGraphragAPI
|
|
|
|
if [ $GRANT_DEV_ACCESS -eq 1 ]; then
|
|
grantDevAccessToAzureResources
|
|
fi
|
|
|
|
echo "SUCCESS: GraphRAG deployment to resource group $RESOURCE_GROUP complete"
|