From 694e14723ee5343d9dade2ca03092cf0d5707bdc Mon Sep 17 00:00:00 2001 From: DakotaCondos <79940799+DakotaCondos@users.noreply.github.com> Date: Sun, 24 May 2026 15:12:05 -0700 Subject: [PATCH 1/3] Add Robot Simulator IaC module and deploy workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Iac/simulator/: standalone Terraform module for the simulator Container App. Uses data sources for all existing Azure resources (resource group, Container Apps env, ACR, Event Hub namespace). Owns structural Container App config (ingress, registry, scale, identity) while excluding live Event Hub env vars via lifecycle.ignore_changes. - .github/workflows/simulator-deploy.yml: image-only deploy workflow triggered on RobotSimulator/** changes. Preserves live Container App env/secrets — no --set-env-vars. - docs/simulator-deployment.md: deployment guide covering IaC ownership boundary, manual terraform apply steps, workflow trigger, and known limitations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/simulator-deploy.yml | 84 +++++++++++++++++ Iac/simulator/main.tf | 114 +++++++++++++++++++++++ Iac/simulator/outputs.tf | 39 ++++++++ Iac/simulator/providers.tf | 14 +++ Iac/simulator/terraform.tfvars | 8 ++ Iac/simulator/variables.tf | 42 +++++++++ docs/simulator-deployment.md | 121 +++++++++++++++++++++++++ 7 files changed, 422 insertions(+) create mode 100644 .github/workflows/simulator-deploy.yml create mode 100644 Iac/simulator/main.tf create mode 100644 Iac/simulator/outputs.tf create mode 100644 Iac/simulator/providers.tf create mode 100644 Iac/simulator/terraform.tfvars create mode 100644 Iac/simulator/variables.tf create mode 100644 docs/simulator-deployment.md diff --git a/.github/workflows/simulator-deploy.yml b/.github/workflows/simulator-deploy.yml new file mode 100644 index 0000000..7a67d5e --- /dev/null +++ b/.github/workflows/simulator-deploy.yml @@ -0,0 +1,84 @@ +name: Build and Deploy Robot Simulator + +on: + push: + branches: [main] + paths: + - "RobotSimulator/**" + +# Required for OIDC federated identity authentication — no client secrets used +permissions: + id-token: write + contents: read + +env: + RESOURCE_GROUP: ewu-deliverybotsystem-rg + + # ACR — pre-created; admin credentials stored as Container App registry secret + ACR_NAME: DeliverybotCR + ACR_LOGIN_SERVER: deliverybotcr.azurecr.io + + # Container App — pre-created with system-assigned managed identity + CONTAINER_APP_NAME: deliverybot-robot-simulator + + IMAGE_NAME: deliverybot-robot-simulator + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + # ── 1. Checkout source code ─────────────────────────────────────────────── + - name: Checkout repository + uses: actions/checkout@v4 + + # ── 2. Authenticate to Azure via OIDC federated identity ───────────────── + - name: Azure Login (OIDC) + uses: azure/login@v2 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + # ── 3. Build Docker image and push to ACR ──────────────────────────────── + # Build context is RobotSimulator/ to match the docker-compose convention. + # The Dockerfile copies src projects relative to that context root. + - name: Log in to Azure Container Registry + run: az acr login --name "$ACR_NAME" + + - name: Build and push Docker image + run: | + IMAGE_TAG="${ACR_LOGIN_SERVER}/${IMAGE_NAME}:${{ github.sha }}" + echo "Building: $IMAGE_TAG" + docker build \ + -f RobotSimulator/src/DeliveryBot.RobotSimulator.Api/Dockerfile \ + -t "$IMAGE_TAG" \ + RobotSimulator/ + docker push "$IMAGE_TAG" + echo "IMAGE_TAG=$IMAGE_TAG" >> "$GITHUB_ENV" + + # ── 4. Deploy updated image to Container App ───────────────────────────── + # Image-only update — environment variables and secrets already configured + # on the live Container App are intentionally left unchanged. + # Do not add --set-env-vars here; Event Hub transport settings are managed + # outside this workflow until they are brought under IaC. + - name: Update Container App image + run: | + az containerapp update \ + --name "$CONTAINER_APP_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --image "$IMAGE_TAG" + + # ── 5. Output the live deployment URL ───────────────────────────────────── + - name: Print deployment URL + run: | + FQDN=$(az containerapp show \ + --name "$CONTAINER_APP_NAME" \ + --resource-group "$RESOURCE_GROUP" \ + --query properties.configuration.ingress.fqdn -o tsv) + echo "========================================" + echo " Deployment complete!" + echo " App URL : https://${FQDN}" + echo " Health : https://${FQDN}/health" + echo " Bots API : https://${FQDN}/bots" + echo "========================================" diff --git a/Iac/simulator/main.tf b/Iac/simulator/main.tf new file mode 100644 index 0000000..e0f5522 --- /dev/null +++ b/Iac/simulator/main.tf @@ -0,0 +1,114 @@ +# --------------------------------------------------------------------------- +# Data sources — reference existing Azure resources; nothing here is created +# or destroyed by this module. +# --------------------------------------------------------------------------- + +data "azurerm_resource_group" "rg" { + name = var.resource_group_name +} + +data "azurerm_container_app_environment" "env" { + name = var.container_app_env_name + resource_group_name = data.azurerm_resource_group.rg.name +} + +data "azurerm_container_registry" "acr" { + name = var.acr_name + resource_group_name = data.azurerm_resource_group.rg.name +} + +data "azurerm_eventhub_namespace" "evhns" { + name = var.event_hub_namespace_name + resource_group_name = data.azurerm_resource_group.rg.name +} + +# --------------------------------------------------------------------------- +# Robot Simulator Container App +# +# Ownership boundary (Balanced approach): +# OWNED by this module: +# - Container image reference +# - Ingress: external, port 8080 +# - Revision mode: single (stateful in-memory simulator; one replica only) +# - Scale: min 1, max 1 +# - System-assigned managed identity +# - ACR registry reference +# - Non-sensitive env vars: ASPNETCORE_ENVIRONMENT, ASPNETCORE_URLS +# +# NOT owned by this module (managed outside IaC): +# - EventTransport__Mode +# - EventTransport__ConnectionString +# - EventTransport__InputEventHubName +# - EventTransport__OutputEventHubName +# - EventTransport__ConsumerGroup +# - EventTransport__EnableInputConsumer +# - Any Container App secrets +# +# Reason: these env vars and secrets are already live on the Container App +# and must not be overwritten during early IaC rollout. They will be brought +# under IaC management when the project-wide IaC module is established. +# --------------------------------------------------------------------------- + +resource "azurerm_container_app" "simulator" { + name = var.container_app_name + resource_group_name = data.azurerm_resource_group.rg.name + container_app_environment_id = data.azurerm_container_app_environment.env.id + revision_mode = "Single" + + identity { + type = "SystemAssigned" + } + + registry { + server = data.azurerm_container_registry.acr.login_server + } + + template { + container { + name = var.container_app_name + image = "${data.azurerm_container_registry.acr.login_server}/${var.image_name}:${var.image_tag}" + cpu = 0.5 + memory = "1Gi" + + # Non-sensitive runtime environment variables. + # Event Hub transport settings are intentionally excluded — see ownership + # boundary comment above. + env { + name = "ASPNETCORE_ENVIRONMENT" + value = "Production" + } + + env { + name = "ASPNETCORE_URLS" + value = "http://+:8080" + } + } + + # Scale is fixed at exactly one replica. + # The simulator holds bot state in memory; multiple replicas would produce + # independent, conflicting bot fleets with no shared state. + min_replicas = 1 + max_replicas = 1 + } + + ingress { + external_enabled = true + target_port = 8080 + + traffic_weight { + percentage = 100 + latest_revision = true + } + } + + lifecycle { + # Prevent Terraform from overwriting env vars or secrets that are managed + # outside this module (e.g., Event Hub connection settings set via portal + # or CLI). Remove this ignore block once those settings are brought under + # IaC management. + ignore_changes = [ + template[0].container[0].env, + secret, + ] + } +} diff --git a/Iac/simulator/outputs.tf b/Iac/simulator/outputs.tf new file mode 100644 index 0000000..ec9f8c6 --- /dev/null +++ b/Iac/simulator/outputs.tf @@ -0,0 +1,39 @@ +output "container_app_name" { + description = "Name of the simulator Container App." + value = azurerm_container_app.simulator.name +} + +output "container_app_fqdn" { + description = "Fully-qualified domain name of the simulator Container App ingress." + value = azurerm_container_app.simulator.ingress[0].fqdn +} + +output "container_app_url" { + description = "Base HTTPS URL for the simulator." + value = "https://${azurerm_container_app.simulator.ingress[0].fqdn}" +} + +output "health_url" { + description = "Health check URL for the simulator." + value = "https://${azurerm_container_app.simulator.ingress[0].fqdn}/health" +} + +output "acr_login_server" { + description = "ACR login server used for the simulator image." + value = data.azurerm_container_registry.acr.login_server +} + +output "image_reference" { + description = "Full image reference deployed to the Container App." + value = "${data.azurerm_container_registry.acr.login_server}/${var.image_name}:${var.image_tag}" +} + +output "event_hub_namespace_name" { + description = "Name of the Event Hub namespace referenced by the simulator." + value = data.azurerm_eventhub_namespace.evhns.name +} + +output "event_hub_namespace_fqdn" { + description = "Fully-qualified Event Hub namespace host (for reference)." + value = "${data.azurerm_eventhub_namespace.evhns.name}.servicebus.windows.net" +} diff --git a/Iac/simulator/providers.tf b/Iac/simulator/providers.tf new file mode 100644 index 0000000..0fce778 --- /dev/null +++ b/Iac/simulator/providers.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.5" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.110" + } + } +} + +provider "azurerm" { + features {} +} diff --git a/Iac/simulator/terraform.tfvars b/Iac/simulator/terraform.tfvars new file mode 100644 index 0000000..f22929d --- /dev/null +++ b/Iac/simulator/terraform.tfvars @@ -0,0 +1,8 @@ +resource_group_name = "ewu-deliverybotsystem-rg" +location = "westus" +container_app_env_name = "managedEnvironment-ewudeliverybots-aa2f" +acr_name = "DeliverybotCR" +event_hub_namespace_name = "DeliverybotSimulator-EVHNS" +container_app_name = "deliverybot-robot-simulator" +image_name = "deliverybot-robot-simulator" +image_tag = "latest" diff --git a/Iac/simulator/variables.tf b/Iac/simulator/variables.tf new file mode 100644 index 0000000..2d5e859 --- /dev/null +++ b/Iac/simulator/variables.tf @@ -0,0 +1,42 @@ +variable "resource_group_name" { + description = "Name of the existing resource group that contains all simulator resources." + type = string +} + +variable "location" { + description = "Azure region of the resource group." + type = string +} + +variable "container_app_env_name" { + description = "Name of the existing Container Apps managed environment." + type = string +} + +variable "acr_name" { + description = "Name of the existing Azure Container Registry (without .azurecr.io)." + type = string +} + +variable "event_hub_namespace_name" { + description = "Name of the existing Event Hub namespace used by the simulator." + type = string +} + +variable "container_app_name" { + description = "Name of the simulator Container App." + type = string + default = "deliverybot-robot-simulator" +} + +variable "image_name" { + description = "Container image name (without registry prefix or tag)." + type = string + default = "deliverybot-robot-simulator" +} + +variable "image_tag" { + description = "Container image tag. Typically the deploying commit SHA." + type = string + default = "latest" +} diff --git a/docs/simulator-deployment.md b/docs/simulator-deployment.md new file mode 100644 index 0000000..3d320d0 --- /dev/null +++ b/docs/simulator-deployment.md @@ -0,0 +1,121 @@ +# Simulator Deployment + +## Overview + +The Robot Simulator is deployed as a Container App (`deliverybot-robot-simulator`) in the `ewu-deliverybotsystem-rg` resource group. Deployment is handled by a GitHub Actions workflow that builds a new container image and updates the running Container App image only. + +Infrastructure configuration for the simulator is codified in `Iac/simulator/`. + +--- + +## IaC Module (`Iac/simulator/`) + +The simulator Terraform module uses a **balanced ownership** approach. It owns the Container App's structural configuration while deliberately excluding the live Event Hub transport settings. + +### What this module owns + +| Resource | Managed properties | +|---|---| +| `azurerm_container_app` | Image reference, ingress (external, port 8080), revision mode (Single), scale (min 1 / max 1), system-assigned identity, ACR registry reference, `ASPNETCORE_ENVIRONMENT`, `ASPNETCORE_URLS` | + +### What this module does NOT own + +The following settings are currently managed outside IaC (via Azure Portal or CLI) and are intentionally excluded from the Terraform module: + +| Setting | Reason | +|---|---| +| `EventTransport__Mode` | Already configured on live Container App | +| `EventTransport__ConnectionString` | Secret — managed outside IaC | +| `EventTransport__InputEventHubName` | Already configured on live Container App | +| `EventTransport__OutputEventHubName` | Already configured on live Container App | +| `EventTransport__ConsumerGroup` | Already configured on live Container App | +| `EventTransport__EnableInputConsumer` | Already configured on live Container App | +| Container App secrets | Managed outside IaC | + +A `lifecycle { ignore_changes }` block in `main.tf` ensures Terraform does not overwrite these settings when applying changes. + +These settings will be brought under IaC management when the project-wide Terraform module is established. + +### Referenced existing resources + +These resources are read via `data` sources; they are not created or destroyed by this module: + +| Resource | Name | +|---|---| +| Resource group | `ewu-deliverybotsystem-rg` | +| Container Apps environment | `managedEnvironment-ewudeliverybots-aa2f` | +| Azure Container Registry | `DeliverybotCR` | +| Event Hub namespace | `DeliverybotSimulator-EVHNS` | + +### Running the module manually + +Prerequisites: +- Terraform >= 1.5 +- Azure CLI authenticated (`az login`) with sufficient permissions on `ewu-deliverybotsystem-rg` + +```powershell +cd Iac/simulator + +terraform init +terraform plan +terraform apply +``` + +To deploy a specific image tag: + +```powershell +terraform apply -var="image_tag=" +``` + +--- + +## GitHub Actions Workflow + +File: `.github/workflows/simulator-deploy.yml` + +### Trigger + +The workflow runs automatically on push to `main` when any file under `RobotSimulator/` changes. + +```yaml +on: + push: + branches: [main] + paths: + - "RobotSimulator/**" +``` + +### What the workflow does + +1. Checks out the repository +2. Authenticates to Azure using OIDC federated identity (no client secrets) +3. Logs in to `DeliverybotCR` container registry +4. Builds the simulator Docker image from: + - Dockerfile: `RobotSimulator/src/DeliveryBot.RobotSimulator.Api/Dockerfile` + - Build context: `RobotSimulator/` + - Tag: `deliverybotcr.azurecr.io/deliverybot-robot-simulator:` +5. Pushes the image to ACR +6. Updates the `deliverybot-robot-simulator` Container App image **only** +7. Prints the Container App URL and health endpoint + +### What the workflow does NOT change + +The workflow uses `az containerapp update --image` with no `--set-env-vars`. This means all existing environment variables and secrets on the live Container App (including Event Hub transport settings) are preserved exactly as-is after every deployment. + +### Required GitHub secrets + +These secrets must be configured in the repository before the workflow can run. They are already used by the existing `botNetApi-deploy.yml` workflow. + +| Secret | Description | +|---|---| +| `AZURE_CLIENT_ID` | Client ID of the Azure AD app registration used for OIDC | +| `AZURE_TENANT_ID` | Azure AD tenant ID | +| `AZURE_SUBSCRIPTION_ID` | Azure subscription ID | + +--- + +## Known Limitations + +- **Event Hub env vars are not in IaC.** If the Container App is recreated from scratch using this Terraform module, the Event Hub transport settings will not be present. They must be re-applied manually or via CLI until they are brought under IaC. +- **No Terraform remote state configured.** The module currently has no `backend` block. A remote backend (e.g., Azure Storage) should be added before team use. +- **Image tag in tfvars defaults to `latest`.** For production deployments, always supply the specific commit SHA as `image_tag`. From fc9f84a07d132a8700ae9e57af1aaeaabd8d3578 Mon Sep 17 00:00:00 2001 From: DakotaCondos <79940799+DakotaCondos@users.noreply.github.com> Date: Sun, 24 May 2026 15:37:21 -0700 Subject: [PATCH 2/3] Add Robot Simulator Terraform module and deploy workflow - Add simulator-only Terraform module under Iac/simulator/ - Reference existing Azure resources and preserve live Event Hub env vars - Add GitHub Actions workflow for image-only simulator deployment - Document simulator deployment and future IaC integration --- docs/simulator-Iac-integration-guide | 130 +++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/simulator-Iac-integration-guide diff --git a/docs/simulator-Iac-integration-guide b/docs/simulator-Iac-integration-guide new file mode 100644 index 0000000..6e6f418 --- /dev/null +++ b/docs/simulator-Iac-integration-guide @@ -0,0 +1,130 @@ +**Simulator IaC Integration Guide** + +This document describes what must change to merge the standalone simulator Terraform module into the eventual project-wide IaC. + +## Current state + +The simulator IaC currently lives in `Iac/simulator/` as a self-contained Terraform setup. It: + +- references existing Azure resources with `data` sources +- owns the simulator Container App's structural configuration +- intentionally excludes the live Event Hub env vars and secrets +- uses local state and a module-local provider config +- is deployed independently from the rest of the project + +That shape was chosen to unblock simulator infrastructure work before the full IaC stack exists. + +## What needs to change later + +### 1. Move the simulator module under the project root IaC + +The simulator should become a child module of the eventual root Terraform configuration. + +Likely changes: + +- move `Iac/simulator/` into the project's module layout +- remove `providers.tf` from the child module +- remove `terraform.tfvars` from the child module +- call the simulator module from the root module + +Example: + +```hcl +module "simulator" { + source = "./modules/simulator" + + resource_group_name = var.resource_group_name + location = var.location + container_app_env_name = var.container_app_env_name + acr_name = var.acr_name + event_hub_namespace_name = var.event_hub_namespace_name + container_app_name = var.simulator_container_app_name + image_name = var.simulator_image_name + image_tag = var.simulator_image_tag +} +``` + +### 2. Add remote state + +The current module has no backend. The final IaC should use a remote backend, likely Azure Storage. + +Required steps: + +- define the backend at the root level +- migrate local state if any exists +- keep the simulator module stateless except for managed resources + +### 3. Bring Event Hub settings into Terraform + +The biggest merge item is the simulator's Event Hub configuration. + +The following env vars are currently excluded and must eventually be declared in IaC: + +- `EventTransport__Mode` +- `EventTransport__InputEventHubName` +- `EventTransport__OutputEventHubName` +- `EventTransport__ConsumerGroup` +- `EventTransport__EnableInputConsumer` +- `EventTransport__ConnectionString` or its managed-identity replacement + +Recommended end state: + +- non-secret values become plain Terraform env vars +- the connection string moves to a secret or is removed entirely by switching to managed identity +- the `ignore_changes` block is removed once Terraform fully owns the env set + +### 4. Import the live Container App + +The existing `deliverybot-robot-simulator` Container App already exists in Azure. + +When the root IaC is ready, import it into state before first apply: + +```powershell +terraform import module.simulator.azurerm_container_app.simulator /subscriptions//resourceGroups/ewu-deliverybotsystem-rg/providers/Microsoft.App/containerApps/deliverybot-robot-simulator +``` + +This avoids Terraform trying to create a duplicate resource. + +### 5. Align workflow deployment with Terraform ownership + +The current workflow updates the Container App image only. + +Later, it should be adjusted to match the final IaC ownership model: + +- keep image tag updates sourced from CI +- decide whether Terraform or the workflow is the deploy authority +- remove the image-only `az containerapp update` path if Terraform becomes the single source of truth + +### 6. Add project-wide conventions + +The final IaC will likely standardize: + +- provider configuration +- variable naming +- backend configuration +- tagging +- naming conventions +- secret handling + +The simulator module should be adapted to those conventions rather than preserved as a standalone exception. + +## What can stay the same + +- the simulator Container App sizing and ingress choices +- the existing Azure resource names +- the module's general variable intent +- the workflow trigger on `RobotSimulator/**` +- the image naming convention + +## Cleanup list when merging + +- remove simulator-local `providers.tf` +- remove simulator-local `terraform.tfvars` +- remove `lifecycle.ignore_changes` once env vars are managed in Terraform +- migrate state to remote backend +- import the live Container App +- update the workflow to match the final Terraform deployment flow + +## Goal + +The end state should be a single project-wide IaC stack where the simulator is just one module, not a separate deployment island. \ No newline at end of file From ff57a62ecd4faf34b5aab20fc98c45c1cb982ff8 Mon Sep 17 00:00:00 2001 From: DakotaCondos <79940799+DakotaCondos@users.noreply.github.com> Date: Tue, 26 May 2026 11:04:52 -0700 Subject: [PATCH 3/3] Update repo readme image path --- readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 499c948..f716b87 100644 --- a/readme.md +++ b/readme.md @@ -4,4 +4,5 @@ Autonomous "food" delivery robots deliver refreshments to patrons wherever they This repo includes all solution components including the robot software, web applications, backend services and infrastructure as code. -![Delivvery Bot](./frontend/public/hero.png) +![Delivvery Bot](./frontend/customer-webapp/public/hero.png) +