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
84 changes: 84 additions & 0 deletions .github/workflows/simulator-deploy.yml
Original file line number Diff line number Diff line change
@@ -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 "========================================"
114 changes: 114 additions & 0 deletions Iac/simulator/main.tf
Original file line number Diff line number Diff line change
@@ -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,
]
}
}
39 changes: 39 additions & 0 deletions Iac/simulator/outputs.tf
Original file line number Diff line number Diff line change
@@ -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"
}
14 changes: 14 additions & 0 deletions Iac/simulator/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.5"

required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.110"
}
}
}

provider "azurerm" {
features {}
}
8 changes: 8 additions & 0 deletions Iac/simulator/terraform.tfvars
Original file line number Diff line number Diff line change
@@ -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"
42 changes: 42 additions & 0 deletions Iac/simulator/variables.tf
Original file line number Diff line number Diff line change
@@ -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"
}
Loading