Skip to content
Merged
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
43 changes: 41 additions & 2 deletions .github/workflows/deploy-ansible.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,44 @@ on:
- 'services/**'
- 'infra/**'
- 'ansible/**'
- '.github/workflows/ansible-deploy.yml'
- '.github/workflows/deploy-ansible.yml'

workflow_dispatch:

permissions:
id-token: write
contents: read

env:
RESOURCE_GROUP: my-app-rg
VM_NAME: my-vm

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Azure login (OIDC)
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

- name: Resolve VM public IP
run: |
IP=$(az vm show -d \
--resource-group "$RESOURCE_GROUP" \
--name "$VM_NAME" \
--query publicIps -o tsv)
if [ -z "$IP" ]; then
echo "::error::VM has no public IP. Run 'Start environment' first."
exit 1
fi
echo "VM_IP=$IP" >> "$GITHUB_ENV"

- name: Set up Python
uses: actions/setup-python@v5
with:
Expand All @@ -37,8 +64,20 @@ jobs:
known_hosts: 'placeholder' # Will be overwritten by ssh-keyscan next

- name: Scan VM Host Key
run: ssh-keyscan -H "$VM_IP" >> ~/.ssh/known_hosts

- name: Write inventory with the resolved IP
run: |
ssh-keyscan -H 4.223.70.80 >> ~/.ssh/known_hosts
cat > infra/inventory.ini <<EOF
[web]
$VM_IP

[all:vars]
ansible_user=azureuser
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
ansible_python_interpreter=/usr/bin/python3.10
EOF

- name: Run Playbook
run: |
Expand Down
87 changes: 87 additions & 0 deletions .github/workflows/vm-start.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Start environment

# Brings the Azure VM online for a demo. Creates a public IP, attaches it,
# starts the VM (containers auto-restart via `restart: unless-stopped`), and
# arms an auto-shutdown timer so the VM deallocates itself after `minutes`.
# The public URL is printed in the run summary.

on:
workflow_dispatch:
inputs:
minutes:
description: "Minutes to keep the VM running before it auto-shuts-down (e.g. 5, 60, 120)"
required: true
default: "120"

permissions:
id-token: write
contents: read

env:
RESOURCE_GROUP: my-app-rg
VM_NAME: my-vm
NIC_NAME: my-nic
IP_CONFIG: internal
PIP_NAME: my-vm-pip
LOCATION: spaincentral

jobs:
start:
runs-on: ubuntu-latest
steps:
- name: Azure login (OIDC)
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

- name: Create public IP (if missing)
run: |
az network public-ip create \
--resource-group "$RESOURCE_GROUP" \
--name "$PIP_NAME" \
--location "$LOCATION" \
--sku Standard \
--allocation-method Static \
--only-show-errors

- name: Attach public IP to the VM's NIC
run: |
az network nic ip-config update \
--resource-group "$RESOURCE_GROUP" \
--nic-name "$NIC_NAME" \
--name "$IP_CONFIG" \
--public-ip-address "$PIP_NAME" \
--only-show-errors

- name: Start the VM
run: az vm start --resource-group "$RESOURCE_GROUP" --name "$VM_NAME"

- name: Arm auto-shutdown timer (now + minutes, UTC)
env:
MINUTES: ${{ github.event.inputs.minutes }}
run: |
SHUTDOWN=$(date -u -d "+${MINUTES} minutes" +%H%M)
az vm auto-shutdown \
--resource-group "$RESOURCE_GROUP" \
--name "$VM_NAME" \
--time "$SHUTDOWN"
echo "SHUTDOWN_UTC=$SHUTDOWN" >> "$GITHUB_ENV"

- name: Publish the URL
env:
MINUTES: ${{ github.event.inputs.minutes }}
run: |
IP=$(az network public-ip show \
--resource-group "$RESOURCE_GROUP" \
--name "$PIP_NAME" \
--query ipAddress -o tsv)
{
echo "### 🚀 Environment starting"
echo ""
echo "**URL:** http://$IP (allow ~1–2 min for containers to come up)"
echo ""
echo "Auto-shutdown armed for **${SHUTDOWN_UTC} UTC** (in ${MINUTES} min)."
echo "Run **Stop environment** to end early and release the IP."
} >> "$GITHUB_STEP_SUMMARY"
54 changes: 54 additions & 0 deletions .github/workflows/vm-stop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Stop environment

# Deallocates the VM (stops compute billing) and deletes the public IP, so the
# only ongoing cost while idle is the OS disk. Safe to run anytime; the VM's
# data and containers survive on the disk and return on the next start.

on:
workflow_dispatch:

permissions:
id-token: write
contents: read

env:
RESOURCE_GROUP: my-app-rg
VM_NAME: my-vm
NIC_NAME: my-nic
IP_CONFIG: internal
PIP_NAME: my-vm-pip

jobs:
stop:
runs-on: ubuntu-latest
steps:
- name: Azure login (OIDC)
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

- name: Deallocate the VM
run: az vm deallocate --resource-group "$RESOURCE_GROUP" --name "$VM_NAME"

- name: Detach and delete the public IP
run: |
az network nic ip-config update \
--resource-group "$RESOURCE_GROUP" \
--nic-name "$NIC_NAME" \
--name "$IP_CONFIG" \
--remove publicIpAddress \
--only-show-errors || true
az network public-ip delete \
--resource-group "$RESOURCE_GROUP" \
--name "$PIP_NAME" \
--only-show-errors || true

- name: Summary
run: |
{
echo "### 🛑 Environment stopped"
echo ""
echo "VM deallocated and public IP released."
} >> "$GITHUB_STEP_SUMMARY"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

Rancher deployment (Kubernetes): https://devsecops.stud.k8s.aet.cit.tum.de

Azure deployment (Docker Compose): http://4.223.70.80/
Azure deployment (Docker Compose): Unfortunately, all of us almost ran out of credits on Azure. Therefore, we can only serve it on-demand: [Run the `vm-start` workflow](https://github.com/AET-DevOps26/team-devsecops/actions/workflows/vm-start.yml) to activate an Azure instance for 2 hours (IP address will be printed in the logs).

Coverage reports: https://aet-devops26.github.io/team-devsecops/

Expand Down
5 changes: 5 additions & 0 deletions infra/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ networks:
services:
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
volumes:
Expand All @@ -18,6 +19,7 @@ services:

py-recipe-service:
build: ../services/py-recipe-service
restart: unless-stopped
env_file: .env
expose:
- "8080"
Expand All @@ -26,6 +28,7 @@ services:

py-help-service:
build: ../services/py-help-service
restart: unless-stopped
env_file: .env
expose:
- "8080"
Expand Down Expand Up @@ -54,6 +57,7 @@ services:

spring-api:
build: ../services/spring-api
restart: unless-stopped
env_file: .env
expose:
- "8080"
Expand All @@ -77,6 +81,7 @@ services:

web-client:
build: ../web-client
restart: unless-stopped
expose:
- "8080"
networks:
Expand Down
2 changes: 1 addition & 1 deletion infra/id_rsa.pub
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCEoD/FM5r6/dGFx5/5wu29zPXq7MyYe85wgCz7yXukKMBGjqcRhyzuiBDNxUON3SL3xL1T6H9/DA0wmSBoAvvY6QeKdBz5LTLl29Ot84ZI3WivwJdOo/uqokOqU+HDGHQ46yUWe6NcBndwha3QkBU7++HaSzt9WuLozUjvWWkplYWlGtB3khqngJGCDo8Oh63ekXuJYdbfwi7g/rAO9/ipVyCGsTg8oJAItPyQYd8MgBYQMmOoO3k073XDFaBI9TO+WZ3m8eSmzyTyV8pQlFn/Yl7d63SWOLcfGwgIHlFNWhUyoB+FlBKBF3LUpi+negOWbHzL5sKzaJGj9UMvT1MVPcAmd6Fgv7bHbtdhN8x4tCrYLlVKDHTEp1m8aIaa6Y7lIsyvL8R1hH9jAQU4xgN6do7d/fiIMxQrdWIeMSbbvDRGdYI/e51zF7m7Iq5l6gvu8IWU7tX40zyXIaLdPIAYH+DyhQOCCk2CY7QUlU9+Br3SrRGbcpcXV3Gj7IiIf6IDSrr4ZXlB8VbOMpQu9yywHhsdLTiu50HNEqtKMDriAPUzZqRlHj+ZZfQY+Xmbs0lPhPMy5hY3kxcOsnG26jwbEf2iY2z5I8YNM0Jxb6KP0CSHcKHiVlopBaofre3md9Yn0Wq7FWga0h3NfM8wpbooTTq4o5NGD6Y6LrEl4n9glw== dave@wolfsburg
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQChTNMZffIK4v5uX2CCJJHHGjmSBgVfv070mPy1FdmYkpgPAHBgr8gjko5lmpAzcw4eai/xz5d/L4JNFn/H9JE1jJqVt2DCnRbtLJmHHFEMyWEpmIOJy2cLepdBHCsv8aLmo15emJCw0bCOV/YEATJKreCLZgfRGZK/XKI2Ymy1tGGKJN7TsGp84rLc5c9jRlxOiV6s8zt2d9GT3v6ksjcrAvXR1tT2GRxelTvnska4G1M7dTBlBjZa2UEyccA4ZyQGrdRjHU6OmH0Chp5nz2btSxw+J1vO7DkdYDKW8zUcpOwwCySc3Ip7dWllwalaWimRT+2H7x10xoy/g7lCjYWdL7/c9eSr2bdg4iym4ZsJ3JYspKmTgBmVcDurMdQgCWk4j0Dz4GDil+sqecpp6ZjZkydrr2lF9jiQpYChkCb8bVC7eRpnBNYLMo7LaZdj7PLZdUbME6Iiw+Ryv4djj1uWTiJsypShwjQ/VSCOHiBTynlMCZS/Gl0pK8vAQf3rckBektPPagje4FrNiWnOZ9AX5Kj4ttsE/rYJuK7QIgB6J0sD62rNwzA1SF/811wOu4EEE0pd1Vbut8iAFkxvQXkT7Llw5lLH56D+ZdzBYkfQ62UrC1ZA4ZPOZM3F1uE2bdYRlQwPRDxZI4zBjJZv1mTEcbRpP0Fz5HUzv9B+L0Q1Jw== jakob@jakob-desktop
4 changes: 3 additions & 1 deletion infra/inventory.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# .github/workflows/deploy-ansible.yml) regenerates the IP at runtime. For a local Ansible
# run, replace __VM_IP__ with the IP printed by the "Start environment" workflow.
[web]
4.223.70.80
__VM_IP__

[all:vars]
ansible_user=azureuser
Expand Down
20 changes: 5 additions & 15 deletions infra/main.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# allowed regions: ["swedencentral","polandcentral","austriaeast","spaincentral","germanywestcentral"]
resource "azurerm_resource_group" "rg" {
name = "my-app-rg"
location = "swedencentral"
location = "spaincentral"
}

resource "azurerm_virtual_network" "vnet" {
Expand All @@ -18,14 +17,6 @@ resource "azurerm_subnet" "subnet" {
address_prefixes = ["10.0.2.0/24"]
}

resource "azurerm_public_ip" "pip" {
name = "my-vm-pip"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Static"
sku = "Standard"
}

resource "azurerm_network_security_group" "nsg" {
name = "my-nsg"
location = azurerm_resource_group.rg.location
Expand Down Expand Up @@ -92,15 +83,14 @@ resource "azurerm_network_interface" "nic" {
name = "internal"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.pip.id
}

lifecycle {
ignore_changes = [ip_configuration[0].public_ip_address_id]
}
}

resource "azurerm_network_interface_security_group_association" "nsg_assoc" {
network_interface_id = azurerm_network_interface.nic.id
network_security_group_id = azurerm_network_security_group.nsg.id
}

output "public_ip" {
value = azurerm_public_ip.pip.ip_address
}
Loading