Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,45 @@ Both features are **gated** — if a status stays `Pending` for more than a few
./scripts/register-providers.ps1 -SubscriptionId <your-sub-id>
```

### Run the all-in-one quickstart on macOS

Install PowerShell 7, Azure CLI, and Python 3, then run the same cross-platform script from a `pwsh` terminal:

```bash
brew install --cask powershell
brew install azure-cli python

az login
pwsh -File ./scripts/quickstart.ps1 \
-SubscriptionId <your-sub-id> \
-ResourceGroup <your-resource-group>
```

The script accepts the same parameters on Windows and macOS. It creates an isolated Python virtual environment and runs the demo with Azure AD authentication; no API key is required.

### Required Azure access

The simplest option is to give the deploying customer **Owner** on the target subscription for the deployment window. Owner covers resource-group creation, provider/feature registration, resource deployment, and the role assignment created by this template. Remove or reduce that access after validation.

For a least-privilege customer deployment, split the work:

1. A subscription administrator registers `Microsoft.Storage`, `Microsoft.CognitiveServices`, and the gated `Microsoft.CognitiveServices/OpenAI.ContextCacheAllowed` feature, and creates the target resource group.
2. At the target **resource-group scope**, grant the customer **Contributor** plus **Role Based Access Control Administrator**.
3. The customer runs `quickstart.ps1 -SkipPrerequisiteRegistration` against that existing resource group. This avoids subscription-level checks that a resource-group-scoped identity may not be allowed to read.

The two resource-group roles are both required by the current template:

| Role | Why it is needed |
|---|---|
| **Contributor** | Creates the Azure OpenAI account/deployment, Context Cache account/container, and ARM deployment record. |
| **Role Based Access Control Administrator** | Creates the `Cognitive Services OpenAI User` role assignment used by the keyless demo. `Contributor` explicitly cannot create role assignments. |

Provider registration requires the provider's `/register/action` permission at subscription scope; built-in **Contributor** and **Owner** include it. Preview-feature registration is also subscription-scoped and may additionally require Microsoft allow-list approval. Email **azurecontextcacherp@microsoft.com** if `OpenAI.ContextCacheAllowed` remains `Pending`.

After deployment, a person or workload calling the model with Microsoft Entra ID needs **Cognitive Services OpenAI User** on the Azure OpenAI account. The template grants it automatically to the deploying user when it creates a new account; the quickstart attempts the same grant when reusing an existing account.

RBAC alone does not guarantee deployment success. Confirm that the subscription has Azure OpenAI S0/model capacity for the selected region and is approved for the Context Cache preview before the customer session.

---

## What gets created
Expand Down
133 changes: 75 additions & 58 deletions scripts/quickstart.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
.PARAMETER SkipPython
Skip the auto venv + pip install step (assumes you already activated an env with the demo deps).

.PARAMETER SkipPrerequisiteRegistration
Skip subscription-level provider and preview-feature checks. Use only after a subscription
administrator has confirmed that all prerequisites are registered.

.EXAMPLE
./quickstart.ps1

Expand All @@ -61,7 +65,8 @@ param(
[string] $ExistingAoaiAccountName,
[int] $Runs = 6,
[switch] $SkipDemo,
[switch] $SkipPython
[switch] $SkipPython,
[switch] $SkipPrerequisiteRegistration
)

$ErrorActionPreference = 'Stop'
Expand Down Expand Up @@ -166,66 +171,70 @@ if ($NamePrefix -and ($NamePrefix -notmatch '^[a-z0-9]{3,12}$')) {
# ----- 5. Provider + feature registration -----
Write-Step "Validating RP + preview-feature registration"

$checks = @(
@{ Kind='provider'; Namespace='Microsoft.Storage'; Name=$null }
@{ Kind='provider'; Namespace='Microsoft.CognitiveServices'; Name=$null }
@{ Kind='feature'; Namespace='Microsoft.CognitiveServices'; Name='OpenAI.ContextCacheAllowed' }
)

function Get-RegState($c) {
if ($c.Kind -eq 'provider') {
return az provider show --namespace $c.Namespace --query registrationState -o tsv 2>$null
}
return az feature show --namespace $c.Namespace --name $c.Name --query properties.state -o tsv 2>$null
}
if ($SkipPrerequisiteRegistration) {
Write-Warn "Skipping subscription-level registration checks; assuming an administrator completed them."
} else {
$checks = @(
@{ Kind='provider'; Namespace='Microsoft.Storage'; Name=$null }
@{ Kind='provider'; Namespace='Microsoft.CognitiveServices'; Name=$null }
@{ Kind='feature'; Namespace='Microsoft.CognitiveServices'; Name='OpenAI.ContextCacheAllowed' }
)

$toRegister = @()
foreach ($c in $checks) {
$state = Get-RegState $c
$label = if ($c.Kind -eq 'provider') { "provider $($c.Namespace)" } else { "feature $($c.Namespace)/$($c.Name)" }
if ($state -eq 'Registered') {
Write-Ok ("{0,-65} {1}" -f $label, $state)
} else {
Write-Warn ("{0,-65} {1}" -f $label, $(if ($state) { $state } else { 'NotRegistered' }))
$toRegister += $c
function Get-RegState($c) {
if ($c.Kind -eq 'provider') {
return az provider show --namespace $c.Namespace --query registrationState -o tsv 2>$null
}
return az feature show --namespace $c.Namespace --name $c.Name --query properties.state -o tsv 2>$null
}
}

if ($toRegister.Count -gt 0) {
Write-Info "Registering missing items..."
foreach ($c in $toRegister) {
if ($c.Kind -eq 'provider') {
az provider register --namespace $c.Namespace | Out-Null
$toRegister = @()
foreach ($c in $checks) {
$state = Get-RegState $c
$label = if ($c.Kind -eq 'provider') { "provider $($c.Namespace)" } else { "feature $($c.Namespace)/$($c.Name)" }
if ($state -eq 'Registered') {
Write-Ok ("{0,-65} {1}" -f $label, $state)
} else {
az feature register --namespace $c.Namespace --name $c.Name | Out-Null
Write-Warn ("{0,-65} {1}" -f $label, $(if ($state) { $state } else { 'NotRegistered' }))
$toRegister += $c
}
}
Write-Info "Waiting for all to reach 'Registered' (up to 10 min, refreshes every 20s)..."
$deadline = (Get-Date).AddMinutes(10)
do {
Start-Sleep -Seconds 20
$stillPending = @()

if ($toRegister.Count -gt 0) {
Write-Info "Registering missing items..."
foreach ($c in $toRegister) {
$state = Get-RegState $c
$label = if ($c.Kind -eq 'provider') { "provider $($c.Namespace)" } else { "feature $($c.Namespace)/$($c.Name)" }
if ($state -ne 'Registered') {
$stillPending += "$label = $state"
if ($c.Kind -eq 'provider') {
az provider register --namespace $c.Namespace | Out-Null
} else {
az feature register --namespace $c.Namespace --name $c.Name | Out-Null
}
}
if ($stillPending.Count -eq 0) { break }
Write-Info ("[{0}] still pending: {1}" -f (Get-Date -Format HH:mm:ss), ($stillPending -join '; '))
} while ((Get-Date) -lt $deadline)
Write-Info "Waiting for all to reach 'Registered' (up to 10 min, refreshes every 20s)..."
$deadline = (Get-Date).AddMinutes(10)
do {
Start-Sleep -Seconds 20
$stillPending = @()
foreach ($c in $toRegister) {
$state = Get-RegState $c
$label = if ($c.Kind -eq 'provider') { "provider $($c.Namespace)" } else { "feature $($c.Namespace)/$($c.Name)" }
if ($state -ne 'Registered') {
$stillPending += "$label = $state"
}
}
if ($stillPending.Count -eq 0) { break }
Write-Info ("[{0}] still pending: {1}" -f (Get-Date -Format HH:mm:ss), ($stillPending -join '; '))
} while ((Get-Date) -lt $deadline)

$finalPending = @()
foreach ($c in $toRegister) {
if ((Get-RegState $c) -ne 'Registered') { $finalPending += $c }
}
if ($finalPending.Count -gt 0) {
Write-Warn "Some items did not reach 'Registered' in time. Gated preview features may require allow-listing - email azurecontextcacherp@microsoft.com."
$cont = Read-NonEmpty "Continue with deployment anyway? (y/N)" 'N'
if ($cont -notmatch '^[Yy]') { throw "Aborted due to incomplete registration." }
} else {
Write-Ok "All providers and features are Registered."
$finalPending = @()
foreach ($c in $toRegister) {
if ((Get-RegState $c) -ne 'Registered') { $finalPending += $c }
}
if ($finalPending.Count -gt 0) {
Write-Warn "Some items did not reach 'Registered' in time. Gated preview features may require allow-listing - email azurecontextcacherp@microsoft.com."
$cont = Read-NonEmpty "Continue with deployment anyway? (y/N)" 'N'
if ($cont -notmatch '^[Yy]') { throw "Aborted due to incomplete registration." }
} else {
Write-Ok "All providers and features are Registered."
}
}
}

Expand Down Expand Up @@ -302,31 +311,39 @@ if (-not (Test-Path (Join-Path $demoDir 'code_reviewer_demo.py'))) {

Write-Step "Running keyless demo (DefaultAzureCredential, $Runs iterations)"

if (-not (Get-Command python -ErrorAction SilentlyContinue)) {
Write-Warn "python not in PATH; skipping demo. Install Python 3.10+ to run it."
$pythonCommand = Get-Command python3 -ErrorAction SilentlyContinue
if (-not $pythonCommand) {
$pythonCommand = Get-Command python -ErrorAction SilentlyContinue
}
if (-not $pythonCommand) {
Write-Warn "python3/python not in PATH; skipping demo. Install Python 3.10+ to run it."
return
}
$demoPython = $pythonCommand.Source

Push-Location $demoDir
try {
if (-not $SkipPython) {
$venv = Join-Path $demoDir '.venv'
if (-not (Test-Path $venv)) {
Write-Info "Creating venv at .venv ..."
python -m venv .venv
& $demoPython -m venv $venv
}
$venvPythonRelativePath = if ($IsWindows) { 'Scripts/python.exe' } else { 'bin/python' }
$demoPython = Join-Path $venv $venvPythonRelativePath
if (-not (Test-Path $demoPython)) {
throw "Virtual-environment Python not found at '$demoPython'. Remove '$venv' and re-run."
}
$activate = Join-Path $venv 'Scripts\Activate.ps1'
. $activate
Write-Info "Installing demo requirements (quiet)..."
pip install -q -r requirements.txt
& $demoPython -m pip install -q -r requirements.txt
}

$env:AOAI_ENDPOINT = $endpoint
$env:AOAI_DEPLOYMENT = $aoaiDeploymentName
Remove-Item Env:AOAI_API_KEY -ErrorAction SilentlyContinue

Write-Host ""
python code_reviewer_demo.py --aad --runs $Runs
& $demoPython code_reviewer_demo.py --aad --runs $Runs
$demoExit = $LASTEXITCODE
} finally {
Pop-Location
Expand Down
Loading