181 lines
7.3 KiB
Bash
Executable File
181 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =============================================================================
|
|
# 04-deploy.sh (Linux)
|
|
# Builds the Docker image, pushes it to Artifact Registry, and deploys it
|
|
# to Cloud Run — all in one command.
|
|
#
|
|
# Usage:
|
|
# ./GCR/scripts/04-deploy.sh # deploy with tag = git short SHA
|
|
# ./GCR/scripts/04-deploy.sh my-tag # deploy with a custom tag
|
|
#
|
|
# Prerequisites:
|
|
# 1. GCR/.env exists and is filled in (copy from GCR/.env.example)
|
|
# 2. 01-login.sh has been run (gcloud auth + Docker configured)
|
|
# 3. 02-setup-project.sh has been run (APIs enabled, repo created)
|
|
# 4. 03-create-secrets.sh has been run (MongoDB secret created)
|
|
# 5. Docker daemon is running locally
|
|
#
|
|
# Windows users: run GCR/scripts/04-deploy.ps1 in PowerShell instead.
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
if [[ "$(uname -s)" != "Linux" ]]; then
|
|
echo "ERROR: This script is for Linux only."
|
|
echo "Windows users: run GCR/scripts/04-deploy.ps1"
|
|
exit 1
|
|
fi
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
|
|
# ── Load .env ─────────────────────────────────────────────────────────────────
|
|
ENV_FILE="$SCRIPT_DIR/../.env"
|
|
if [[ ! -f "$ENV_FILE" ]]; then
|
|
echo "ERROR: $ENV_FILE not found."
|
|
echo "Copy GCR/.env.example to GCR/.env and fill in your values first."
|
|
exit 1
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
source "$ENV_FILE"
|
|
|
|
: "${GCP_PROJECT_ID:?GCP_PROJECT_ID is not set in .env}"
|
|
: "${GCP_REGION:?GCP_REGION is not set in .env}"
|
|
: "${GCP_REPOSITORY:?GCP_REPOSITORY is not set in .env}"
|
|
: "${SERVICE_NAME:?SERVICE_NAME is not set in .env}"
|
|
: "${MONGODB_DATABASE_NAME:?MONGODB_DATABASE_NAME is not set in .env}"
|
|
|
|
# Note: MONGODB_CONNECTION_STRING is stored in Secret Manager (mongodb-connection-string)
|
|
# See GCR/README.md for Secret Manager setup
|
|
|
|
secret_setup_ready() {
|
|
local service_account
|
|
service_account="serviceAccount:${GCP_PROJECT_ID}@appspot.gserviceaccount.com"
|
|
|
|
gcloud secrets describe "mongodb-connection-string" --project="$GCP_PROJECT_ID" >/dev/null 2>&1 || return 1
|
|
|
|
gcloud secrets get-iam-policy "mongodb-connection-string" \
|
|
--project="$GCP_PROJECT_ID" \
|
|
--flatten="bindings[].members" \
|
|
--filter="bindings.role=roles/secretmanager.secretAccessor AND bindings.members=${service_account}" \
|
|
--format="value(bindings.members)" 2>/dev/null \
|
|
| grep -Fxq "$service_account"
|
|
}
|
|
|
|
if ! secret_setup_ready; then
|
|
echo ""
|
|
echo ">>> Required secrets are not fully configured yet."
|
|
read -rp " Run GCR/scripts/03-create-secrets.sh now? [y/N]: " RUN_SECRET_SETUP
|
|
if [[ "$RUN_SECRET_SETUP" =~ ^[Yy]$ ]]; then
|
|
bash "$SCRIPT_DIR/03-create-secrets.sh"
|
|
else
|
|
echo ""
|
|
echo "ERROR: Deployment requires secret setup first."
|
|
echo "Run: bash GCR/scripts/03-create-secrets.sh"
|
|
exit 1
|
|
fi
|
|
|
|
if ! secret_setup_ready; then
|
|
echo ""
|
|
echo "ERROR: Secret setup check still failing after running 03-create-secrets.sh."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# ── Determine image tag ────────────────────────────────────────────────────────
|
|
TAG="${1:-}"
|
|
if [[ -z "$TAG" ]]; then
|
|
# Default to git short SHA if inside a git repo; otherwise use timestamp
|
|
if git -C "$REPO_ROOT" rev-parse --short HEAD &>/dev/null; then
|
|
TAG="$(git -C "$REPO_ROOT" rev-parse --short HEAD)"
|
|
else
|
|
TAG="$(date +%Y%m%d%H%M%S)"
|
|
fi
|
|
fi
|
|
|
|
REGISTRY="${GCP_REGION}-docker.pkg.dev"
|
|
IMAGE_URI="${REGISTRY}/${GCP_PROJECT_ID}/${GCP_REPOSITORY}/${SERVICE_NAME}:${TAG}"
|
|
|
|
echo "================================================================"
|
|
echo " Htmx → Cloud Run deployment"
|
|
echo "================================================================"
|
|
echo " Project: $GCP_PROJECT_ID"
|
|
echo " Region: $GCP_REGION"
|
|
echo " Service: $SERVICE_NAME"
|
|
echo " Image: $IMAGE_URI"
|
|
echo "================================================================"
|
|
echo ""
|
|
|
|
# ── Step 1: Ensure package-lock.json exists (required for `npm ci`) ───────────
|
|
LOCKFILE="$REPO_ROOT/Htmx.ApiDemo/package-lock.json"
|
|
if [[ ! -f "$LOCKFILE" ]]; then
|
|
echo ">>> package-lock.json not found. Generating it now..."
|
|
echo " (This requires node + npm to be installed locally)"
|
|
(cd "$REPO_ROOT/Htmx.ApiDemo" && npm install --package-lock-only)
|
|
echo " package-lock.json generated. Commit it to the repository."
|
|
echo ""
|
|
fi
|
|
|
|
# ── Step 2: Build the Docker image ────────────────────────────────────────────
|
|
echo ">>> Building Docker image..."
|
|
echo " Context: $REPO_ROOT"
|
|
echo " Dockerfile: GCR/Dockerfile"
|
|
echo ""
|
|
|
|
# Build from repo root so the COPY instructions can reach both
|
|
# Htmx.ApiDemo/ and Htmx.SourceGenerator/ directories.
|
|
docker build \
|
|
--file "$REPO_ROOT/GCR/Dockerfile" \
|
|
--tag "$IMAGE_URI" \
|
|
"$REPO_ROOT"
|
|
|
|
echo ""
|
|
echo ">>> Image built: $IMAGE_URI"
|
|
|
|
# ── Step 3: Push image to Artifact Registry ───────────────────────────────────
|
|
echo ""
|
|
echo ">>> Pushing image to Artifact Registry..."
|
|
docker push "$IMAGE_URI"
|
|
echo ">>> Push complete."
|
|
|
|
# ── Step 4: Deploy to Cloud Run via docker-compose.yml ───────────────────────
|
|
echo ""
|
|
echo ">>> Deploying to Cloud Run..."
|
|
|
|
# Export variables consumed by docker-compose.yml substitution
|
|
export IMAGE_URI
|
|
export MONGODB_DATABASE_NAME
|
|
|
|
gcloud run services replace "$REPO_ROOT/GCR/docker-compose.yml" \
|
|
--region="$GCP_REGION" \
|
|
--project="$GCP_PROJECT_ID"
|
|
|
|
# ── Step 4b: Inject MongoDB connection string from Secret Manager ────────────
|
|
echo ""
|
|
echo ">>> 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.
|
|
echo ""
|
|
echo ">>> 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 ─────────────────────────────────────────────────────────
|
|
echo ""
|
|
SERVICE_URL=$(gcloud run services describe "$SERVICE_NAME" \
|
|
--region="$GCP_REGION" \
|
|
--project="$GCP_PROJECT_ID" \
|
|
--format="value(status.url)")
|
|
|
|
echo "================================================================"
|
|
echo " Deployment complete!"
|
|
echo " Service URL: $SERVICE_URL"
|
|
echo "================================================================"
|