Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9d18b84
Add Order Service — Place an Order, Get Status, Order History, CI/CD …
npcjake May 25, 2026
90c707e
Add code comments for clarity
npcjake May 26, 2026
7a84ba6
Add file-level comments to all Order Service files
npcjake May 26, 2026
1a99d35
Add unit tests for Order Service business logic
npcjake May 26, 2026
9a064ab
Add .gitignore to test project and remove tracked build artifacts
npcjake May 26, 2026
4f23d25
Switch Order Service to self-managed SQL server
npcjake May 26, 2026
4ec52ae
Add workflow_dispatch to order service pipeline for manual testing
npcjake May 26, 2026
44e0840
Trigger CI on order-service branch for pipeline testing
npcjake May 26, 2026
416abf8
Set BotNetApi base URL in appsettings
npcjake May 26, 2026
bafb475
Revert branch trigger — pipeline runs on main only per team config
npcjake May 26, 2026
db53972
Minor comment cleanup in OrderItem
npcjake May 26, 2026
99d7f9c
Add test step to CI/CD pipeline before Docker build
npcjake May 26, 2026
9d7b3cd
Inject Event Hub connection string via GitHub secret in pipeline
npcjake May 26, 2026
c53f2c9
Re-add workflow_dispatch for manual pipeline testing on order-service…
npcjake May 26, 2026
8e7a628
Add pull_request trigger to workflow
npcjake May 26, 2026
1a3c3d6
Trigger PR workflow
npcjake May 26, 2026
9a29c79
Nest OrderService projects under a single parent folder
npcjake May 28, 2026
8908076
Add Terraform for Order Service Container App
npcjake May 28, 2026
672f742
Run Order Service Terraform from the deploy workflow
npcjake May 28, 2026
70520d7
Fix sensitive map in for_each for Terraform 1.9.x
npcjake May 28, 2026
804b0b7
Skip resource provider registration in Terraform
npcjake May 28, 2026
07290eb
Split Order Service CI into infra and deploy pipelines
npcjake May 28, 2026
89d5802
Enable Swagger UI in all environments
npcjake May 28, 2026
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/orderservice-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Build and Deploy Order Service

# Builds the Order Service image and rolls it out to the Container App.
# Infrastructure (the app itself, its env vars/secrets) is owned by the
# separate orderservice-iac.yml pipeline — this workflow only ships the image.
# The Container App must already exist (provisioned by the IaC pipeline).

on:
push:
branches: [main]
paths:
- "OrderService/**"
- ".github/workflows/orderservice-deploy.yml"
pull_request:
branches: [main]
paths:
- "OrderService/**"
- ".github/workflows/orderservice-deploy.yml"
workflow_dispatch:

# Required for OIDC federated identity — no client secrets stored
permissions:
id-token: write
contents: read

env:
RESOURCE_GROUP: ewu-deliverybotsystem-rg
ACR_NAME: DeliverybotCR
ACR_LOGIN_SERVER: deliverybotcr.azurecr.io
CONTAINER_APP_NAME: deliverybot-order-service
IMAGE_NAME: orderservice

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# 1. Check out the code
- name: Checkout repository
uses: actions/checkout@v4

# 2. Run tests — pipeline fails here if any test fails
- name: Run tests
run: dotnet test OrderService/OrderService.Tests/OrderService.Tests.csproj --configuration Release

# 3. Log into Azure using OIDC (no passwords — GitHub proves its identity via token)
- 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 }}

# 4. Build and push the Docker image to the shared ACR
- 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 -t "$IMAGE_TAG" -f OrderService/OrderService/Dockerfile OrderService
docker push "$IMAGE_TAG"
echo "IMAGE_TAG=$IMAGE_TAG" >> "$GITHUB_ENV"

# 5. Roll out the new image. Env vars/secrets are owned by Terraform, so
# this only updates the running image tag.
- name: Update Container App image
run: |
az containerapp update \
--name "$CONTAINER_APP_NAME" \
--resource-group "$RESOURCE_GROUP" \
--image "$IMAGE_TAG"

# 6. Print the live 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 " Order Service live at: https://${FQDN}"
echo " Place Order: POST https://${FQDN}/api/orders"
echo "========================================"
76 changes: 76 additions & 0 deletions .github/workflows/orderservice-iac.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Order Service - Infrastructure

# Provisions the Order Service Container App with Terraform. Runs `plan` on PRs
# (for review) and `apply` only on merge to main. Auth uses the OIDC federated
# identity Phil configured — no client secrets stored.

on:
push:
branches: [main]
paths:
- "Iac/order-service/**"
- ".github/workflows/orderservice-iac.yml"
pull_request:
branches: [main]
paths:
- "Iac/order-service/**"
- ".github/workflows/orderservice-iac.yml"
workflow_dispatch:

permissions:
id-token: write
contents: read

env:
TFSTATE_STORAGE_ACCOUNT: dbstfstate01
TFSTATE_CONTAINER: tfstate

jobs:
terraform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./Iac/order-service
env:
ARM_USE_OIDC: "true"
ARM_USE_AZUREAD: "true"
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
TF_VAR_sql_connection_string: "Server=tcp:jacob-orderservice-sql2.database.windows.net,1433;Initial Catalog=OrderServiceDb;Authentication=Active Directory Managed Identity;"
TF_VAR_eventhub_connection_string: ${{ secrets.AZURE_EVENTHUB_CONNECTION_STRING }}
steps:
- name: Checkout repository
uses: actions/checkout@v4

- 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 }}

# Idempotent — safe to run every time.
- name: Ensure TF state container exists
run: |
az storage container create \
--name "$TFSTATE_CONTAINER" \
--account-name "$TFSTATE_STORAGE_ACCOUNT" \
--auth-mode login \
--only-show-errors

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: "1.9.5"

- name: Terraform Init
run: terraform init -input=false

- name: Terraform Plan
run: terraform plan -input=false -out=tfplan

# Apply only on merge to main — PRs stop at plan for review.
- name: Terraform Apply
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: terraform apply -input=false tfplan
86 changes: 86 additions & 0 deletions Iac/order-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Order Service — Infrastructure (Terraform)

Provisions the **Order Service** Azure Container App (`deliverybot-order-service`).

It reuses the team's shared infrastructure (resource group, Container App
Environment, and ACR) via `data` sources, and only owns the Order Service app
itself. The app is created through the reusable [`container-app`](./modules/container-app)
module.

## Layout

```
order-service/
├── providers.tf # terraform + azurerm + remote state (dbstfstate01)
├── main.tf # data sources for shared infra + module call
├── variables.tf
├── outputs.tf
└── modules/
└── container-app/ # reusable Azure Container App module
```

## What it creates

- `azurerm_container_app.deliverybot-order-service` with:
- a **system-assigned managed identity**
- **ACR pull** via the `acr-password` secret + `registry` block (admin creds)
- external **ingress** on port 8080
- env vars: `ASPNETCORE_ENVIRONMENT`, `BotNetApi__BaseUrl`
- secret-backed env vars: `ConnectionStrings__DefaultConnection`,
`EventHub__ConnectionString`

The **image tag is owned by the CD pipeline**, not Terraform — the module sets
an initial `:latest` image and `ignore_changes` on it so `terraform apply`
doesn't revert the running revision the pipeline deployed.

## Required inputs (sensitive — supplied by the pipeline, never committed)

| Variable | Source |
|---|---|
| `sql_connection_string` | built from the SQL server/db + Managed Identity auth |
| `eventhub_connection_string` | `AZURE_EVENTHUB_CONNECTION_STRING` GitHub secret |

Pass them as `TF_VAR_sql_connection_string` / `TF_VAR_eventhub_connection_string`.

## Usage

```bash
cd Iac/order-service
terraform init
terraform plan
terraform apply
```

Auth is via OIDC (`azure/login@v2`) in CI; locally, `az login` works with
`use_oidc` disabled or `ARM_*` env vars set.

## Importing the existing app

The `deliverybot-order-service` Container App was originally created by hand.
Before the first `apply`, import it so Terraform adopts it instead of trying to
create a duplicate:

```bash
terraform import \
module.order_service_app.azurerm_container_app.this \
/subscriptions/<SUB_ID>/resourceGroups/ewu-deliverybotsystem-rg/providers/Microsoft.App/containerApps/deliverybot-order-service
```

Then run `terraform plan` and reconcile any diff (e.g. tags) before applying.

## Open decision — SQL server

The running app's connection string points at `jacob-orderservice-sql2`, a
server created manually and **not** in any Terraform. The shared root Iac
defines `OrderServiceDb` on `deliverybotsystem-sql` instead. Decide whether to:
- consolidate onto the shared `deliverybotsystem-sql`, or
- add `jacob-orderservice-sql2` to Terraform.

Either way, the chosen server's connection string is passed via
`sql_connection_string`; this stack does not yet manage the database itself.

## Follow-up

The deploy workflow currently sets env vars with `az containerapp update
--set-env-vars`. Once Terraform owns the env vars, the workflow should be
trimmed to **only update the image tag**, to avoid drift between the two.
14 changes: 14 additions & 0 deletions Iac/order-service/imports.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# One-time adoption of the pre-existing Container App into Terraform state.
#
# The deliverybot-order-service app was originally created by hand, so the
# first `terraform apply` must IMPORT it instead of trying to create a
# duplicate (azurerm refuses to create over an existing resource). This import
# block lets CI's service principal do that automatically on the first apply —
# no manual out-of-band `terraform import` needed.
#
# SAFE TO DELETE after the first successful apply has run in CI (the resource
# will already be in remote state; the block then becomes a no-op).
import {
to = module.order_service_app.azurerm_container_app.this
id = "${data.azurerm_resource_group.rg.id}/providers/Microsoft.App/containerApps/${var.container_app_name}"
}
55 changes: 55 additions & 0 deletions Iac/order-service/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Order Service infrastructure.
#
# Reuses the team's shared resource group, Container App Environment, and ACR
# (all created by the root Iac). This stack only owns the Order Service
# Container App itself, provisioned through the reusable container-app module.

data "azurerm_resource_group" "rg" {
name = var.resource_group_name
}

data "azurerm_container_app_environment" "env" {
name = var.container_app_environment_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
}

module "order_service_app" {
source = "./modules/container-app"

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

# Pull from the shared ACR using admin credentials (same pattern as the
# BotNet API and Robot Simulator apps).
acr_login_server = data.azurerm_container_registry.acr.login_server
acr_username = data.azurerm_container_registry.acr.admin_username
acr_password = data.azurerm_container_registry.acr.admin_password

container_name = "orderservice"
image = "${data.azurerm_container_registry.acr.login_server}/${var.image_name}:latest"
target_port = 8080

# Secrets are referenced by env vars below.
secrets = {
"sql-connection-string" = var.sql_connection_string
"eventhub-connection-string" = var.eventhub_connection_string
}

env_vars = {
"ASPNETCORE_ENVIRONMENT" = "Production"
"BotNetApi__BaseUrl" = var.botnet_api_url
}

secret_env_vars = {
"ConnectionStrings__DefaultConnection" = "sql-connection-string"
"EventHub__ConnectionString" = "eventhub-connection-string"
}

tags = var.tags
}
Loading
Loading