GCR deployment testing in progress - content type issue still remaining.

This commit is contained in:
2026-05-05 14:42:03 +05:00
parent 40fe69ed65
commit f8112f897e
22 changed files with 348 additions and 2431 deletions
+52 -151
View File
@@ -1,112 +1,54 @@
# =============================================================================
# 04-deploy.ps1 (Windows)
# Builds the Docker image, pushes it to Artifact Registry, and deploys it
# to Cloud Run — all in one command.
#
# Usage:
# .\GCR\scripts\04-deploy.ps1 # deploy with tag = git short SHA
# .\GCR\scripts\04-deploy.ps1 -Tag my-tag # deploy with a custom tag
#
# Prerequisites:
# 1. GCR\.env exists and is filled in (copy from GCR\.env.example)
# 2. 01-login.ps1 has been run (gcloud auth + Docker configured)
# 3. 02-setup-project.ps1 has been run (APIs enabled, repo created)
# 4. 03-create-secrets.ps1 has been run (MongoDB secret created)
# 5. Docker Desktop is running
#
# Linux users: run GCR/scripts/04-deploy.sh instead.
# =============================================================================
#Requires -Version 5.1
[CmdletBinding()]
param(
[string]$Tag = ""
)
#Requires -Version 5.1
param([string]$Tag)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ErrorActionPreference = 'Continue'
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RepoRoot = (Resolve-Path (Join-Path $ScriptDir "..\..")).Path
$EnvFile = Join-Path $ScriptDir "..\\.env"
# ── Load .env ─────────────────────────────────────────────────────────────────
if (-not (Test-Path $EnvFile)) {
Write-Error "ERROR: $EnvFile not found.`nCopy GCR\.env.example to GCR\.env and fill in your values first."
exit 1
}
if (-not (Test-Path $EnvFile)) { Write-Error "ERROR: $EnvFile not found."; exit 1 }
$config = @{}
foreach ($line in Get-Content $EnvFile) {
if ($line -match '^\s*$' -or $line -match '^\s*#') { continue }
if ($line -match '^([^=]+)=(.*)$') {
$config[$Matches[1].Trim()] = $Matches[2].Trim()
}
if ($line -match '^([^=]+)=(.*)$') { $config[$Matches[1].Trim()] = $Matches[2].Trim() }
}
$GCP_PROJECT_ID = $config['GCP_PROJECT_ID'] ?? ''
$GCP_REGION = $config['GCP_REGION'] ?? ''
$GCP_REPOSITORY = $config['GCP_REPOSITORY'] ?? ''
$SERVICE_NAME = $config['SERVICE_NAME'] ?? ''
$MONGODB_DATABASE_NAME = $config['MONGODB_DATABASE_NAME'] ?? 'HtmxAppDb'
$GCP_PROJECT_ID = if ($config['GCP_PROJECT_ID']) { $config['GCP_PROJECT_ID'] } else { '' }
$GCP_REGION = if ($config['GCP_REGION']) { $config['GCP_REGION'] } else { '' }
$GCP_REPOSITORY = if ($config['GCP_REPOSITORY']) { $config['GCP_REPOSITORY'] } else { '' }
$SERVICE_NAME = if ($config['SERVICE_NAME']) { $config['SERVICE_NAME'] } else { '' }
$MONGODB_DATABASE_NAME = if ($config['MONGODB_DATABASE_NAME']) { $config['MONGODB_DATABASE_NAME'] } else { 'HtmxAppDb' }
# Note: MONGODB_CONNECTION_STRING is stored in Secret Manager (mongodb-connection-string)
if (-not $GCP_PROJECT_ID) { Write-Error "GCP_PROJECT_ID not set"; exit 1 }
if (-not $GCP_REGION) { Write-Error "GCP_REGION not set"; exit 1 }
if (-not $GCP_REPOSITORY) { Write-Error "GCP_REPOSITORY not set"; exit 1 }
if (-not $SERVICE_NAME) { Write-Error "SERVICE_NAME not set"; exit 1 }
if (-not $GCP_PROJECT_ID) { Write-Error "GCP_PROJECT_ID is not set in .env"; exit 1 }
if (-not $GCP_REGION) { Write-Error "GCP_REGION is not set in .env"; exit 1 }
if (-not $GCP_REPOSITORY) { Write-Error "GCP_REPOSITORY is not set in .env"; exit 1 }
if (-not $SERVICE_NAME) { Write-Error "SERVICE_NAME is not set in .env"; exit 1 }
# Note: MONGODB_CONNECTION_STRING is stored in Secret Manager
function Test-SecretsReady {
$serviceAccount = "serviceAccount:$GCP_PROJECT_ID@appspot.gserviceaccount.com"
try {
gcloud secrets describe mongodb-connection-string --project=$GCP_PROJECT_ID 2>$null | Out-Null
} catch {
return $false
}
$binding = gcloud secrets get-iam-policy mongodb-connection-string `
--project=$GCP_PROJECT_ID `
--flatten="bindings[].members" `
--filter="bindings.role=roles/secretmanager.secretAccessor AND bindings.members=$serviceAccount" `
--format="value(bindings.members)" 2>$null
return ($binding -match [regex]::Escape($serviceAccount))
}
if (-not (Test-SecretsReady)) {
Write-Host ""
Write-Host ">>> Required secrets are not fully configured yet."
$runSecretSetup = Read-Host " Run GCR\scripts\03-create-secrets.ps1 now? [y/N]"
if ($runSecretSetup -match '^[Yy]$') {
gcloud secrets describe mongodb-connection-string --project=$GCP_PROJECT_ID 2>$null | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host ">>> Required secrets not configured."
$ans = Read-Host " Run 03-create-secrets.ps1 now? [y/N]"
if ($ans -match '^[Yy]$') {
& (Join-Path $ScriptDir "03-create-secrets.ps1")
gcloud secrets describe mongodb-connection-string --project=$GCP_PROJECT_ID 2>$null | Out-Null
if ($LASTEXITCODE -ne 0) { Write-Error "Secret setup failed."; exit 1 }
} else {
Write-Host ""
Write-Error "Deployment requires secret setup first. Run: .\GCR\scripts\03-create-secrets.ps1"
exit 1
}
if (-not (Test-SecretsReady)) {
Write-Host ""
Write-Error "Secret setup check still failing after running 03-create-secrets.ps1."
exit 1
Write-Error "Run 03-create-secrets.ps1 first."; exit 1
}
}
$bindings = gcloud secrets get-iam-policy mongodb-connection-string --project=$GCP_PROJECT_ID --flatten='bindings[].members' --filter='bindings.role=roles/secretmanager.secretAccessor' --format='value(bindings.members)' 2>$null
if ([string]::IsNullOrWhiteSpace($bindings)) {
Write-Error "No service account has secretAccessor access. Run 03-create-secrets.ps1 first."
exit 1
}
# ── Determine image tag ────────────────────────────────────────────────────────
if (-not $Tag) {
# Default to git short SHA if inside a git repo; otherwise use timestamp
try {
$Tag = (git -C $RepoRoot rev-parse --short HEAD 2>$null).Trim()
} catch { }
if (-not $Tag) {
$Tag = (Get-Date -Format "yyyyMMddHHmmss")
}
try { $Tag = (git rev-parse --short HEAD 2>$null).Trim() } catch { }
if ([string]::IsNullOrWhiteSpace($Tag)) { $Tag = (Get-Date -Format "yyyyMMdd-HHmmss") }
}
$REGISTRY = "$GCP_REGION-docker.pkg.dev"
$IMAGE_URI = "$REGISTRY/$GCP_PROJECT_ID/$GCP_REPOSITORY/${SERVICE_NAME}:$Tag"
$IMAGE = "$GCP_REGION-docker.pkg.dev/$GCP_PROJECT_ID/$GCP_REPOSITORY/htmx-demo-app:$Tag"
$contextDir = Split-Path -Parent (Split-Path -Parent $ScriptDir)
Write-Host "================================================================"
Write-Host " Htmx -> Cloud Run deployment"
@@ -114,81 +56,40 @@ Write-Host "================================================================"
Write-Host " Project: $GCP_PROJECT_ID"
Write-Host " Region: $GCP_REGION"
Write-Host " Service: $SERVICE_NAME"
Write-Host " Image: $IMAGE_URI"
Write-Host " Image: $IMAGE"
Write-Host "================================================================"
Write-Host ""
# ── Step 1: Ensure package-lock.json exists (required for `npm ci`) ───────────
$LockFile = Join-Path $RepoRoot "Htmx.ApiDemo\package-lock.json"
if (-not (Test-Path $LockFile)) {
Write-Host ">>> package-lock.json not found. Generating it now..."
Write-Host " (This requires node + npm to be installed locally)"
Push-Location (Join-Path $RepoRoot "Htmx.ApiDemo")
npm install --package-lock-only
Pop-Location
Write-Host " package-lock.json generated. Commit it to the repository."
Write-Host ""
docker info 2>$null | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Error "Docker is not running. Start Docker Desktop first."
exit 1
}
# ── Step 2: Build the Docker image ────────────────────────────────────────────
Write-Host ">>> Building Docker image..."
Write-Host " Context: $RepoRoot"
Write-Host " Dockerfile: GCR\Dockerfile"
Write-Host ""
# Build from repo root so COPY instructions can reach both project directories.
# Docker on Windows accepts forward slashes in --file.
$DockerFile = Join-Path $RepoRoot "GCR\Dockerfile"
docker build --file $DockerFile --tag $IMAGE_URI $RepoRoot
$env:DOCKER_BUILDKIT = "1"
docker build -t $IMAGE -f "$contextDir\GCR\Dockerfile" $contextDir
if ($LASTEXITCODE -ne 0) { Write-Error "Docker build failed."; exit 1 }
Write-Host ">>> Image built: $IMAGE"
Write-Host ""
Write-Host ">>> Image built: $IMAGE_URI"
# ── Step 3: Push image to Artifact Registry ───────────────────────────────────
Write-Host ""
Write-Host ">>> Pushing image to Artifact Registry..."
docker push $IMAGE_URI
Write-Host ">>> Pushing to Artifact Registry..."
docker push $IMAGE
if ($LASTEXITCODE -ne 0) { Write-Error "Docker push failed."; exit 1 }
Write-Host ">>> Push complete."
# ── Step 4: Deploy to Cloud Run via docker-compose.yml ───────────────────────
Write-Host ""
Write-Host ">>> Deploying to Cloud Run..."
gcloud run services update $SERVICE_NAME --project=$GCP_PROJECT_ID --region=$GCP_REGION --image=$IMAGE --set-env-vars=MONGODB_DATABASE_NAME=$MONGODB_DATABASE_NAME --set-secrets=ConnectionStrings__DefaultConnection=mongodb-connection-string:latest --allow-unauthenticated 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Host " Service not found, creating..."
gcloud run deploy $SERVICE_NAME --project=$GCP_PROJECT_ID --region=$GCP_REGION --image=$IMAGE --set-env-vars=MONGODB_DATABASE_NAME=$MONGODB_DATABASE_NAME --set-secrets=ConnectionStrings__DefaultConnection=mongodb-connection-string:latest --allow-unauthenticated 2>$null
if ($LASTEXITCODE -ne 0) { Write-Error "Deployment failed."; exit 1 }
}
Write-Host ">>> Deployment complete."
# Set env vars consumed by docker-compose.yml variable substitution
$env:IMAGE_URI = $IMAGE_URI
$env:MONGODB_DATABASE_NAME = $MONGODB_DATABASE_NAME
$ComposeFile = Join-Path $RepoRoot "GCR\docker-compose.yml"
gcloud run services replace $ComposeFile `
--region=$GCP_REGION `
--project=$GCP_PROJECT_ID
# ── Step 4b: Inject MongoDB connection string from Secret Manager ────────────
$SERVICE_URL = (gcloud run services describe $SERVICE_NAME --region=$GCP_REGION --project=$GCP_PROJECT_ID --format='value(status.url)' 2>$null).Trim()
Write-Host ""
Write-Host ">>> Injecting MongoDB connection string from Secret Manager..."
gcloud run services update $SERVICE_NAME `
--region=$GCP_REGION `
--project=$GCP_PROJECT_ID `
--set-secrets="ConnectionStrings__DefaultConnection=mongodb-connection-string:latest"
# ── Step 5: Make the service publicly accessible ──────────────────────────────
# Remove this block if you want the service to require authentication.
Write-Host ""
Write-Host ">>> Allowing public (unauthenticated) access to the service..."
gcloud run services add-iam-policy-binding $SERVICE_NAME `
--region=$GCP_REGION `
--project=$GCP_PROJECT_ID `
--member="allUsers" `
--role="roles/run.invoker"
# ── Print service URL ─────────────────────────────────────────────────────────
Write-Host ""
$SERVICE_URL = (gcloud run services describe $SERVICE_NAME `
--region=$GCP_REGION `
--project=$GCP_PROJECT_ID `
--format="value(status.url)").Trim()
Write-Host "================================================================"
Write-Host " Deployment complete!"
Write-Host " Service URL: $SERVICE_URL"
Write-Host " Done! Service URL: $SERVICE_URL"
Write-Host "================================================================"