From 476724e3b8082d8a381db49bcd57270e1b673fa8 Mon Sep 17 00:00:00 2001
From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
Date: Fri, 27 Mar 2026 08:33:26 -0700
Subject: [PATCH 1/4] refactor: review and improve ps
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
---
001-customer-support-agent/README.md | 34 ++---
.../scripts/setup-azure.ps1 | 133 ++++++++++++++++++
.../scripts/setup-azure.sh | 103 +++++++-------
3 files changed, 199 insertions(+), 71 deletions(-)
create mode 100644 001-customer-support-agent/scripts/setup-azure.ps1
diff --git a/001-customer-support-agent/README.md b/001-customer-support-agent/README.md
index d5f5ed7..b21b7e4 100644
--- a/001-customer-support-agent/README.md
+++ b/001-customer-support-agent/README.md
@@ -14,19 +14,15 @@ This sample is a customer support agent application for the fictional **Contoso
Below is a high-level architecture diagram of the application:
-
-
-
+
At the completion of this walkthrough, the application and Radius configuration will look like:
-
-
-
+
## 📁 Repository structure
-```
+```text
├── knowledge-base/ # Contoso policy PDFs for RAG
├── scripts/setup-azure.sh # One-command Azure prerequisite setup
├── src/
@@ -91,10 +87,7 @@ Run the setup script to create an Azure resource group, AKS cluster, service pri
**PowerShell**
```powershell
-
-(Get-Content .\scripts\setup-azure.sh -Raw) -replace "`r`n","`n" | Set-Content .\scripts\setup-azure.sh -Encoding UTF8
-
-bash ./scripts/setup-azure.sh --location westus3 --resource-group customer-support-agent --cluster-name customer-support-agent-aks
+./scripts/setup-azure.ps1 -Location westus3 -ResourceGroupName customer-support-agent -ClusterName customer-support-agent-aks
```
> [!NOTE]
@@ -139,7 +132,7 @@ kubectl get pods -n radius-system
You should see all Radius pods running:
-```
+```text
NAME READY STATUS RESTARTS AGE
applications-rp 1/1 Running 0 1m
bicep-de 1/1 Running 0 1m
@@ -184,7 +177,7 @@ Verify the credential is registered:
rad credential list
```
-```
+```text
PROVIDER REGISTERED
azure true
```
@@ -215,7 +208,7 @@ You can verify the types were created:
rad resource-type list
```
-```
+```text
TYPE NAMESPACE APIVERSION
Applications.Core/applications Applications.Core ["2023-10-01-preview"]
...
@@ -236,6 +229,7 @@ rad bicep publish-extension -f radius/types/agent.yaml --target radius/extension
rad bicep publish-extension -f radius/types/postgreSqlDatabases.yaml --target radius/extensions/radiusdata.tgz
rad bicep publish-extension -f radius/types/blobStorages.yaml --target radius/extensions/radiusstorage.tgz
```
+
### Step 6: Create the Radius Environment
@@ -305,7 +299,7 @@ Now lets look at the recipes added to the environment. A Recipe defines *how* to
rad recipe list
```
-```
+```text
RECIPE TYPE TEMPLATE KIND TEMPLATE
default Radius.AI/agents bicep ghcr.io/radius-project/lab/recipes/agent:1.0
default Radius.Data/postgreSqlDatabases bicep ghcr.io/radius-project/lab/recipes/postgres:1.0
@@ -451,7 +445,7 @@ The deployment will take approximately 15-20 minutes to complete. In the meantim
When the deployment is complete, you should see output similar to:
-```
+```text
Deployment In Progress...
Completed contoso-support-agent Applications.Core/applications
@@ -482,7 +476,7 @@ Here are some things to try that demonstrate the agentic behavior:
**Simple order lookup** (single tool call):
-```
+```text
What's the status of ORD-10001?
```
@@ -490,7 +484,7 @@ The agent will call `lookup_order` and respond with the order details from the d
**Policy question** (knowledge base search):
-```
+```text
What's your return policy for electronics?
```
@@ -498,7 +492,7 @@ The agent will call `search_knowledge_base` and respond using information from t
**Multi-step tool chaining** (multiple tool calls in sequence):
-```
+```text
I want to return the headphones from order ORD-10001. Can you help?
```
@@ -511,7 +505,7 @@ The agent will:
**Escalation** (knowing when to hand off):
-```
+```text
This is unacceptable, I've been waiting 3 weeks and nobody can help me. I want to speak to a manager.
```
diff --git a/001-customer-support-agent/scripts/setup-azure.ps1 b/001-customer-support-agent/scripts/setup-azure.ps1
new file mode 100644
index 0000000..003e64c
--- /dev/null
+++ b/001-customer-support-agent/scripts/setup-azure.ps1
@@ -0,0 +1,133 @@
+#Requires -Modules Az.Accounts, Az.Resources, Az.Aks
+
+<#
+.SYNOPSIS
+ Setup Azure prerequisites for the Customer Support Agent.
+
+.DESCRIPTION
+ Creates: resource group, AKS cluster (if it doesn't exist),
+ service principal, and registers required resource providers.
+ Saves service principal credentials to .azure-sp.env for use by Radius.
+
+.PARAMETER Location
+ Azure region for resources. Default: westus3
+
+.PARAMETER ResourceGroupName
+ Name of the resource group. Default: customer-support-agent
+
+.PARAMETER ClusterName
+ Name of the AKS cluster. Default: customer-support-agent-aks
+
+.EXAMPLE
+ ./scripts/setup-azure.ps1
+ ./scripts/setup-azure.ps1 -Location eastus2
+ ./scripts/setup-azure.ps1 -ResourceGroupName my-rg -ClusterName my-aks
+#>
+
+[CmdletBinding()]
+param(
+ [string]$ResourceGroupName = 'customer-support-agent',
+ [string]$Location = 'westus3',
+ [string]$ClusterName = 'customer-support-agent-aks'
+)
+
+$ErrorActionPreference = 'Stop'
+
+$SpName = 'radius-sp'
+
+# ── 0. Ensure Azure PowerShell is logged in ──────────────────
+Write-Host '==> Checking Azure PowerShell login...'
+$context = Get-AzContext
+if (-not $context) {
+ Write-Host ' Not logged in. Opening browser for login...'
+ Connect-AzAccount
+ $context = Get-AzContext
+}
+
+$SubscriptionId = $context.Subscription.Id
+Write-Host "Subscription: $SubscriptionId"
+Write-Host "Resource Group: $ResourceGroupName"
+Write-Host "Location: $Location"
+Write-Host "AKS Cluster: $ClusterName"
+Write-Host ''
+
+# ── 1. Register resource providers ────────────────────────────
+Write-Host '==> Registering resource providers...'
+$providers = @(
+ 'Microsoft.Storage'
+ 'Microsoft.DBforPostgreSQL'
+ 'Microsoft.ContainerInstance'
+ 'Microsoft.OperationalInsights'
+ 'Microsoft.Search'
+ 'Microsoft.CognitiveServices'
+)
+foreach ($provider in $providers) {
+ Register-AzResourceProvider -ProviderNamespace $provider | Out-Null
+ Write-Host " ✓ $provider"
+}
+
+# ── 2. Create resource group ─────────────────────────────────
+Write-Host ''
+Write-Host "==> Creating resource group '$ResourceGroupName' in '$Location'..."
+New-AzResourceGroup -Name $ResourceGroupName -Location $Location -Force | Out-Null
+
+# ── 3. Create or connect to AKS cluster ──────────────────────
+Write-Host ''
+$existingCluster = Get-AzAksCluster -ResourceGroupName $ResourceGroupName -Name $ClusterName -ErrorAction SilentlyContinue
+if ($existingCluster) {
+ Write-Host "==> AKS cluster '$ClusterName' already exists, getting credentials..."
+ Import-AzAksCredential `
+ -ResourceGroupName $ResourceGroupName `
+ -Name $ClusterName `
+ -Force
+}
+else {
+ Write-Host "==> Creating AKS cluster '$ClusterName' (this takes a few minutes)..."
+ New-AzAksCluster `
+ -ResourceGroupName $ResourceGroupName `
+ -Name $ClusterName `
+ -Location $Location `
+ -NodeCount 1 `
+ -NodeVmSize 'Standard_D2s_v3' `
+ -GenerateSshKey
+
+ Write-Host '==> Getting AKS credentials...'
+ Import-AzAksCredential `
+ -ResourceGroupName $ResourceGroupName `
+ -Name $ClusterName `
+ -Force
+}
+
+# ── 4. Create service principal ──────────────────────────────
+Write-Host ''
+Write-Host "==> Creating service principal '$SpName' with Owner role..."
+$scope = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName"
+
+# Create the AD application and service principal with a password credential
+$sp = New-AzADServicePrincipal `
+ -DisplayName $SpName `
+ -Role 'Owner' `
+ -Scope $scope
+
+$ClientId = $sp.AppId
+$ClientSecret = $sp.PasswordCredentials.SecretText
+$TenantId = $context.Tenant.Id
+
+# ── 5. Save credentials to file ──────────────────────────────
+$envFile = '.azure-sp.env'
+@"
+AZURE_CLIENT_ID=$ClientId
+AZURE_CLIENT_SECRET=$ClientSecret
+AZURE_TENANT_ID=$TenantId
+AZURE_SUBSCRIPTION_ID=$SubscriptionId
+AZURE_RESOURCE_GROUP=$ResourceGroupName
+"@ | Set-Content -Path $envFile -NoNewline
+
+Write-Host ''
+Write-Host "==> Service principal credentials saved to $envFile"
+
+Write-Host ''
+Write-Host '================================================'
+Write-Host ' Azure setup complete!'
+Write-Host '================================================'
+Write-Host ''
diff --git a/001-customer-support-agent/scripts/setup-azure.sh b/001-customer-support-agent/scripts/setup-azure.sh
index 01dbc13..52313c2 100755
--- a/001-customer-support-agent/scripts/setup-azure.sh
+++ b/001-customer-support-agent/scripts/setup-azure.sh
@@ -1,4 +1,5 @@
#!/bin/bash
+
set -euo pipefail
# ── Setup Azure prerequisites for the Customer Support Agent ──
@@ -20,22 +21,22 @@ SP_NAME="radius-sp"
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
- --location)
- LOCATION="$2"
- shift 2
- ;;
- --resource-group)
- RESOURCE_GROUP="$2"
- shift 2
- ;;
- --cluster-name)
- CLUSTER_NAME="$2"
- shift 2
- ;;
- *)
- echo "Unknown option: $1"
- exit 1
- ;;
+ --location)
+ LOCATION="$2"
+ shift 2
+ ;;
+ --resource-group)
+ RESOURCE_GROUP="$2"
+ shift 2
+ ;;
+ --cluster-name)
+ CLUSTER_NAME="$2"
+ shift 2
+ ;;
+ *)
+ echo "Unknown option: $1"
+ exit 1
+ ;;
esac
done
@@ -45,23 +46,23 @@ sanitize() {
printf '%s' "$1" | tr -d '\r'
}
-RESOURCE_GROUP=$(sanitize "$RESOURCE_GROUP")
-LOCATION=$(sanitize "$LOCATION")
-CLUSTER_NAME=$(sanitize "$CLUSTER_NAME")
-SP_NAME=$(sanitize "$SP_NAME")
+RESOURCE_GROUP=$(sanitize "${RESOURCE_GROUP}")
+LOCATION=$(sanitize "${LOCATION}")
+CLUSTER_NAME=$(sanitize "${CLUSTER_NAME}")
+SP_NAME=$(sanitize "${SP_NAME}")
# ── 0. Ensure Azure CLI is logged in ─────────────────────
echo "==> Checking Azure CLI login..."
-if ! az account show &>/dev/null; then
+if ! az account show &> /dev/null; then
echo " Not logged in. Opening browser for login..."
az login
fi
SUBSCRIPTION_ID=$(sanitize "$(az account show --query id -o tsv)")
-echo "Subscription: $SUBSCRIPTION_ID"
-echo "Resource Group: $RESOURCE_GROUP"
-echo "Location: $LOCATION"
-echo "AKS Cluster: $CLUSTER_NAME"
+echo "Subscription: ${SUBSCRIPTION_ID}"
+echo "Resource Group: ${RESOURCE_GROUP}"
+echo "Location: ${LOCATION}"
+echo "AKS Cluster: ${CLUSTER_NAME}"
echo ""
# ── 1. Register resource providers ──────────────────────────
@@ -76,63 +77,63 @@ PROVIDERS=(
Microsoft.ContainerService
)
for provider in "${PROVIDERS[@]}"; do
- az provider register --namespace "$provider" --wait 2>/dev/null || true
- echo " ✓ $provider"
+ az provider register --namespace "${provider}" --wait 2> /dev/null || true
+ echo " ✓ ${provider}"
done
# ── 2. Create resource group ────────────────────────────────
echo ""
-echo "==> Creating resource group '$RESOURCE_GROUP' in '$LOCATION'..."
-az group create --name "$RESOURCE_GROUP" --location "$LOCATION" -o none
+echo "==> Creating resource group '${RESOURCE_GROUP}' in '${LOCATION}'..."
+az group create --name "${RESOURCE_GROUP}" --location "${LOCATION}" -o none
# ── 3. Create or connect to AKS cluster ─────────────────────
echo ""
-if az aks show --resource-group "$RESOURCE_GROUP" --name "$CLUSTER_NAME" &>/dev/null; then
- echo "==> AKS cluster '$CLUSTER_NAME' already exists, getting credentials..."
+if az aks show --resource-group "${RESOURCE_GROUP}" --name "${CLUSTER_NAME}" &> /dev/null; then
+ echo "==> AKS cluster '${CLUSTER_NAME}' already exists, getting credentials..."
az aks get-credentials \
- --resource-group "$RESOURCE_GROUP" \
- --name "$CLUSTER_NAME" \
+ --resource-group "${RESOURCE_GROUP}" \
+ --name "${CLUSTER_NAME}" \
--overwrite-existing
else
- echo "==> Creating AKS cluster '$CLUSTER_NAME' (this takes a few minutes)..."
+ echo "==> Creating AKS cluster '${CLUSTER_NAME}' (this takes a few minutes)..."
az aks create \
- --resource-group "$RESOURCE_GROUP" \
- --name "$CLUSTER_NAME" \
+ --resource-group "${RESOURCE_GROUP}" \
+ --name "${CLUSTER_NAME}" \
--generate-ssh-keys \
--node-count 1 \
--node-vm-size Standard_D2s_v3
echo "==> Getting AKS credentials..."
az aks get-credentials \
- --resource-group "$RESOURCE_GROUP" \
- --name "$CLUSTER_NAME" \
+ --resource-group "${RESOURCE_GROUP}" \
+ --name "${CLUSTER_NAME}" \
--overwrite-existing
fi
# ── 4. Create service principal ─────────────────────────────
echo ""
-echo "==> Creating service principal '$SP_NAME' with Owner role..."
+echo "==> Creating service principal '${SP_NAME}' with Owner role..."
SP_OUTPUT=$(az ad sp create-for-rbac \
- --name "$SP_NAME" \
+ --name "${SP_NAME}" \
--role Owner \
- --scopes /subscriptions/$SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP \
+ --scopes "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}" \
-o json)
-CLIENT_ID=$(echo "$SP_OUTPUT" | jq -r '.appId')
-CLIENT_SECRET=$(echo "$SP_OUTPUT" | jq -r '.password')
-TENANT_ID=$(echo "$SP_OUTPUT" | jq -r '.tenant')
+CLIENT_ID=$(echo "${SP_OUTPUT}" | jq -r '.appId')
+CLIENT_SECRET=$(echo "${SP_OUTPUT}" | jq -r '.password')
+TENANT_ID=$(echo "${SP_OUTPUT}" | jq -r '.tenant')
# ── 5. Save credentials to file ─────────────────────────────
ENV_FILE=".azure-sp.env"
-cat >"$ENV_FILE" < "${ENV_FILE}" << EOF
+AZURE_CLIENT_ID=${CLIENT_ID}
+AZURE_CLIENT_SECRET=${CLIENT_SECRET}
+AZURE_TENANT_ID=${TENANT_ID}
+AZURE_SUBSCRIPTION_ID=${SUBSCRIPTION_ID}
+AZURE_RESOURCE_GROUP=${RESOURCE_GROUP}
EOF
echo ""
-echo "==> Service principal credentials saved to $ENV_FILE"
+echo "==> Service principal credentials saved to ${ENV_FILE}"
echo ""
echo "================================================"
From 3736cadaca2c784e3a18ea56d34cd0424c9818f1 Mon Sep 17 00:00:00 2001
From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
Date: Fri, 27 Mar 2026 08:40:17 -0700
Subject: [PATCH 2/4] feat: enhance AKS cluster creation with SSH key handling
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
---
.../scripts/setup-azure.ps1 | 22 +++++++++++++------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/001-customer-support-agent/scripts/setup-azure.ps1 b/001-customer-support-agent/scripts/setup-azure.ps1
index 003e64c..c00de78 100644
--- a/001-customer-support-agent/scripts/setup-azure.ps1
+++ b/001-customer-support-agent/scripts/setup-azure.ps1
@@ -83,13 +83,21 @@ if ($existingCluster) {
}
else {
Write-Host "==> Creating AKS cluster '$ClusterName' (this takes a few minutes)..."
- New-AzAksCluster `
- -ResourceGroupName $ResourceGroupName `
- -Name $ClusterName `
- -Location $Location `
- -NodeCount 1 `
- -NodeVmSize 'Standard_D2s_v3' `
- -GenerateSshKey
+ $sshKey = Join-Path -Path $HOME -ChildPath '.ssh/id_rsa.pub'
+ $aksParams = @{
+ ResourceGroupName = $ResourceGroupName
+ Name = $ClusterName
+ Location = $Location
+ NodeCount = 1
+ NodeVmSize = 'Standard_D2s_v3'
+ }
+ if (Test-Path $sshKey) {
+ $aksParams['SshKeyValue'] = $sshKey
+ }
+ else {
+ $aksParams['GenerateSshKey'] = $true
+ }
+ New-AzAksCluster @aksParams
Write-Host '==> Getting AKS credentials...'
Import-AzAksCredential `
From 8b2fd30ae2fa302db14e778feec12ed48f5c8e09 Mon Sep 17 00:00:00 2001
From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
Date: Fri, 27 Mar 2026 08:43:04 -0700
Subject: [PATCH 3/4] docs: update README to link Azure Cloud Shell
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
---
001-customer-support-agent/README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/001-customer-support-agent/README.md b/001-customer-support-agent/README.md
index b21b7e4..16b9260 100644
--- a/001-customer-support-agent/README.md
+++ b/001-customer-support-agent/README.md
@@ -59,7 +59,7 @@ Before you begin, you need:
>
> - WSL (recommended)
> - Git Bash
-> - Azure Cloud Shell
+> - [Azure Cloud Shell](https://shell.azure.com/)
>
> `jq` is required for parsing JSON output in Bash. Install it depending on the bash environment. PowerShell users can follow along with minor syntax adjustments (examples provided where needed).
From 291965badc1ef7f8c17f8a3803a06cb1297926cb Mon Sep 17 00:00:00 2001
From: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
Date: Thu, 25 Jun 2026 16:22:04 -0700
Subject: [PATCH 4/4] feat: add Microsoft.ContainerService to resource
providers registration
Signed-off-by: Dariusz Porowski <3431813+DariuszPorowski@users.noreply.github.com>
---
001-customer-support-agent/scripts/setup-azure.ps1 | 1 +
1 file changed, 1 insertion(+)
diff --git a/001-customer-support-agent/scripts/setup-azure.ps1 b/001-customer-support-agent/scripts/setup-azure.ps1
index c00de78..2ee95c3 100644
--- a/001-customer-support-agent/scripts/setup-azure.ps1
+++ b/001-customer-support-agent/scripts/setup-azure.ps1
@@ -60,6 +60,7 @@ $providers = @(
'Microsoft.OperationalInsights'
'Microsoft.Search'
'Microsoft.CognitiveServices'
+ 'Microsoft.ContainerService'
)
foreach ($provider in $providers) {
Register-AzResourceProvider -ProviderNamespace $provider | Out-Null