Skip to content
Closed
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
2 changes: 1 addition & 1 deletion .github/.sync-sha
Original file line number Diff line number Diff line change
@@ -1 +1 @@
fa8eb002b35d34038f5752da6bfdb81291e6587d
36456c7515dc3fc00238c9bf9b23452a53fef27f
5 changes: 4 additions & 1 deletion migration/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ RUN pip install -r requirements.txt

# Copy the migration script and supporting files
COPY v1_to_v2_migration.py .
COPY read_cosmos_data.py .

# Create a non-root user for security
RUN useradd -m -u 1000 migration
Expand All @@ -48,6 +47,10 @@ chown -R migration:migration /app\n\
exec gosu migration python v1_to_v2_migration.py "$@"\n\
' > /app/entrypoint.sh && chmod +x /app/entrypoint.sh

# Copy the interactive-login entrypoint (handles az login inside Docker)
COPY entrypoint-login.sh /app/entrypoint-login.sh
RUN chmod +x /app/entrypoint-login.sh

# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH=/app
Expand Down
192 changes: 192 additions & 0 deletions migration/migrate-docker.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/usr/bin/env pwsh
# ─────────────────────────────────────────────────────────────────────
# migrate-docker.ps1 — Simplified v1→v2 migration via Docker
# ─────────────────────────────────────────────────────────────────────

$ErrorActionPreference = "Stop"

$Green = "`e[32m"
$Blue = "`e[34m"
$Yellow = "`e[33m"
$Red = "`e[31m"
$Cyan = "`e[36m"
$Bold = "`e[1m"
$Reset = "`e[0m"

function Get-AzCliCommand {
$azCommand = Get-Command az -ErrorAction SilentlyContinue
if ($azCommand) { return $azCommand.Source }
foreach ($path in @("C:\Program Files\Microsoft SDKs\Azure\CLI2\wbin\az.cmd", "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin\az.cmd")) {
if (Test-Path $path) { return $path }
}
return $null
}

function Ensure-AzCli {
$azPath = Get-AzCliCommand
if ($azPath) { return $azPath }
Write-Host "${Red}❌ Azure CLI is required on the host for sign-in.${Reset}"
return $null
}

$resourceId = $null
$endpoint = $null
$sourceResourceId = $null
$sourceEndpoint = $null
$passthrough = @()
$listMode = $false

for ($i = 0; $i -lt $args.Count; $i++) {
switch ($args[$i]) {
"--resource-id" { $resourceId = $args[++$i] }
"--endpoint" { $endpoint = $args[++$i] }
"--source-resource-id" { $sourceResourceId = $args[++$i] }
"--source-endpoint" { $sourceEndpoint = $args[++$i] }
"--list" { $listMode = $true; $passthrough += $args[$i] }
default { $passthrough += $args[$i] }
}
}

Write-Host ""
Write-Host "${Blue}${Bold}===============================================================${Reset}"
Write-Host "${Blue}${Bold} v1 to v2 Agent Migration (Docker + simplified)${Reset}"
Write-Host "${Blue}${Bold}===============================================================${Reset}"
Write-Host ""

$azCli = Ensure-AzCli
if (-not $azCli) { exit 1 }

function Parse-ResourceId {
param([string]$Id)
$pattern = "^/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/[^/]+/[^/]+/([^/]+)(?:/projects/([^/]+))?$"
$match = [regex]::Match($Id, $pattern, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
if ($match.Success) {
$projectName = $match.Groups[4].Value
if (-not $projectName) {
$projectName = $match.Groups[3].Value
}
return @{
SubscriptionId = $match.Groups[1].Value
ResourceGroup = $match.Groups[2].Value
ResourceName = $match.Groups[3].Value
ProjectName = $projectName
}
}
return $null
}

if (-not $resourceId) {
Write-Host "${Red}ERROR: Missing required ${Bold}--resource-id${Reset}"
exit 1
}

$target = Parse-ResourceId $resourceId
if (-not $target) {
Write-Host "${Red}ERROR: Could not parse resource ID.${Reset}"
exit 1
}

if (-not $endpoint) {
$endpoint = "https://$($target.ResourceName).services.ai.azure.com/api/projects/$($target.ProjectName)"
}

$source = $null
$sourceEP = $null
if ($sourceResourceId) {
$source = Parse-ResourceId $sourceResourceId
if (-not $source) {
Write-Host "${Red}ERROR: Could not parse --source-resource-id${Reset}"
exit 1
}
if (-not $sourceEndpoint) {
$sourceEP = "https://$($source.ResourceName).services.ai.azure.com/api/projects/$($source.ProjectName)"
} else {
$sourceEP = $sourceEndpoint
}
}

try {
docker info 2>$null | Out-Null
} catch {
Write-Host "${Red}ERROR: Docker is not running. Please start Docker Desktop.${Reset}"
exit 1
}

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Push-Location $scriptDir
docker build -t v1-to-v2-migration .
if ($LASTEXITCODE -ne 0) {
Write-Host "${Red}ERROR: Docker build failed.${Reset}"
Pop-Location
exit $LASTEXITCODE
}

& $azCli account set --subscription $target.SubscriptionId --output none 2>$null
$tenantId = (& $azCli account show --query tenantId -o tsv).Trim()
if (-not $tenantId) {
Write-Host "${Red}ERROR: Could not discover tenant ID.${Reset}"
Pop-Location
exit 1
}

$aiToken = (& $azCli account get-access-token --scope "https://ai.azure.com/.default" --tenant $tenantId --query accessToken -o tsv).Trim()
$openAiCompatToken = (& $azCli account get-access-token --scope "https://cognitiveservices.azure.com/.default" --tenant $tenantId --query accessToken -o tsv).Trim()
if (-not $aiToken -or $aiToken.Length -lt 50) {
Write-Host "${Red}ERROR: Failed to acquire Azure AI token on the host.${Reset}"
Pop-Location
exit 1
}

$migrationArgs = @()
if ($source) {
$migrationArgs += "--project-endpoint", $sourceEP
} else {
$migrationArgs += "--project-endpoint", $endpoint
}
$migrationArgs += "--production-resource", $target.ResourceName
$migrationArgs += "--production-subscription", $target.SubscriptionId
$migrationArgs += "--production-tenant", $tenantId
$migrationArgs += "--production-endpoint", $endpoint
$migrationArgs += $passthrough

$azureConfigDir = "$env:USERPROFILE\.azure"
$dockerEnv = @(
"--network", "host"
"-e", "DOCKER_CONTAINER=true"
"-e", "TARGET_SUBSCRIPTION=$($target.SubscriptionId)"
"-e", "AZ_TOKEN=$aiToken"
"-e", "AZ_TOKEN_SCOPE=https://ai.azure.com/.default"
"-e", "PRODUCTION_TOKEN=$aiToken"
"-v", "${azureConfigDir}:/home/migration/.azure"
)
if ($openAiCompatToken -and $openAiCompatToken.Length -gt 50) {
$dockerEnv += "-e", "OPENAI_COMPAT_TOKEN=$openAiCompatToken"
$dockerEnv += "-e", "OPENAI_COMPAT_TOKEN_SCOPE=https://cognitiveservices.azure.com/.default"
}
if (Test-Path ".env") {
Get-Content ".env" | ForEach-Object {
if ($_ -match "^([^#=]+)=(.*)$") {
$dockerEnv += "-e", "$($matches[1].Trim())=$($matches[2].Trim())"
}
}
}

docker run --rm -it `
@dockerEnv `
v1-to-v2-migration `
@migrationArgs

$exitCode = $LASTEXITCODE
Pop-Location
if ($exitCode -eq 0) {
Write-Host ""
if ($listMode) {
Write-Host "${Green}${Bold}OK: Inventory listing completed successfully!${Reset}"
} else {
Write-Host "${Green}${Bold}OK: Migration completed successfully!${Reset}"
}
} else {
Write-Host ""
Write-Host "${Red}ERROR: Migration failed with exit code: $exitCode${Reset}"
}
exit $exitCode
100 changes: 100 additions & 0 deletions migration/migrate-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
set -euo pipefail

resource_id=""
endpoint=""
source_resource_id=""
source_endpoint=""
list_mode=false
passthrough=()

while [[ $# -gt 0 ]]; do
case "$1" in
--resource-id) resource_id="$2"; shift 2 ;;
--endpoint) endpoint="$2"; shift 2 ;;
--source-resource-id) source_resource_id="$2"; shift 2 ;;
--source-endpoint) source_endpoint="$2"; shift 2 ;;
--list) list_mode=true; passthrough+=("--list"); shift ;;
*) passthrough+=("$1"); shift ;;
esac
done

if [[ -z "$resource_id" ]]; then
echo "❌ Missing required --resource-id"
exit 1
fi

parse_resource_id() {
local id="$1"
python3 - <<'PY' "$id"
import re, sys
value = sys.argv[1]
m = re.match(r'/subscriptions/([^/]+)/resourceGroups/([^/]+)/providers/[^/]+/[^/]+/([^/]+)(?:/projects/([^/]+))?', value)
if not m:
sys.exit(1)
project = m.group(4) if m.group(4) else m.group(3)
print(m.group(1))
print(m.group(2))
print(m.group(3))
print(project)
PY
}

mapfile -t target_parts < <(parse_resource_id "$resource_id") || { echo "❌ Could not parse resource ID"; exit 1; }
target_subscription="${target_parts[0]}"
target_resource_name="${target_parts[2]}"
target_project_name="${target_parts[3]}"
[[ -n "$endpoint" ]] || endpoint="https://${target_resource_name}.services.ai.azure.com/api/projects/${target_project_name}"

if [[ -n "$source_resource_id" ]]; then
mapfile -t source_parts < <(parse_resource_id "$source_resource_id") || { echo "❌ Could not parse --source-resource-id"; exit 1; }
source_resource_name="${source_parts[2]}"
source_project_name="${source_parts[3]}"
[[ -n "$source_endpoint" ]] || source_endpoint="https://${source_resource_name}.services.ai.azure.com/api/projects/${source_project_name}"
fi

command -v az >/dev/null || { echo "❌ Azure CLI not found"; exit 1; }
command -v docker >/dev/null || { echo "❌ Docker not found"; exit 1; }
docker info >/dev/null 2>&1 || { echo "❌ Docker is not running"; exit 1; }

script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$script_dir"
docker build -t v1-to-v2-migration .

az account show >/dev/null 2>&1 || az login --use-device-code >/dev/null
az account set --subscription "$target_subscription"
tenant_id="$(az account show --query tenantId -o tsv | tr -d '\r')"
[[ -n "$tenant_id" ]] || { echo "❌ Could not discover tenant ID"; exit 1; }

ai_token="$(az account get-access-token --scope https://ai.azure.com/.default --tenant "$tenant_id" --query accessToken -o tsv | tr -d '\r')"
compat_token="$(az account get-access-token --scope https://cognitiveservices.azure.com/.default --tenant "$tenant_id" --query accessToken -o tsv | tr -d '\r')"

migration_args=()
if [[ -n "$source_endpoint" ]]; then
migration_args+=("--project-endpoint" "$source_endpoint")
else
migration_args+=("--project-endpoint" "$endpoint")
fi
migration_args+=("--production-resource" "$target_resource_name" "--production-subscription" "$target_subscription" "--production-tenant" "$tenant_id" "--production-endpoint" "$endpoint")
migration_args+=("${passthrough[@]}")

docker_env=(
--network host
-e DOCKER_CONTAINER=true
-e TARGET_SUBSCRIPTION="$target_subscription"
-e AZ_TOKEN="$ai_token"
-e AZ_TOKEN_SCOPE=https://ai.azure.com/.default
-e PRODUCTION_TOKEN="$ai_token"
)
[[ -d "$HOME/.azure" ]] && docker_env+=( -v "$HOME/.azure:/home/migration/.azure" )
if [[ -n "$compat_token" ]]; then
docker_env+=( -e OPENAI_COMPAT_TOKEN="$compat_token" -e OPENAI_COMPAT_TOKEN_SCOPE=https://cognitiveservices.azure.com/.default )
fi
if [[ -f .env ]]; then
while IFS= read -r line; do
[[ "$line" =~ ^[^#=]+=(.*)$ ]] || continue
docker_env+=( -e "$line" )
done < .env
fi

docker run --rm -it "${docker_env[@]}" v1-to-v2-migration "${migration_args[@]}"
Loading
Loading