diff --git a/.github/workflows/deploy-ansible.yml b/.github/workflows/deploy-ansible.yml index e846235..1f75f4c 100644 --- a/.github/workflows/deploy-ansible.yml +++ b/.github/workflows/deploy-ansible.yml @@ -9,10 +9,18 @@ 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 @@ -20,6 +28,25 @@ jobs: - 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: @@ -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 <> "$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" diff --git a/.github/workflows/vm-stop.yml b/.github/workflows/vm-stop.yml new file mode 100644 index 0000000..8ef571b --- /dev/null +++ b/.github/workflows/vm-stop.yml @@ -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" diff --git a/README.md b/README.md index 168ae6d..f2a4c42 100644 --- a/README.md +++ b/README.md @@ -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/ diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 122d166..31e0476 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -5,6 +5,7 @@ networks: services: nginx: image: nginx:alpine + restart: unless-stopped ports: - "80:80" volumes: @@ -18,6 +19,7 @@ services: py-recipe-service: build: ../services/py-recipe-service + restart: unless-stopped env_file: .env expose: - "8080" @@ -26,6 +28,7 @@ services: py-help-service: build: ../services/py-help-service + restart: unless-stopped env_file: .env expose: - "8080" @@ -54,6 +57,7 @@ services: spring-api: build: ../services/spring-api + restart: unless-stopped env_file: .env expose: - "8080" @@ -77,6 +81,7 @@ services: web-client: build: ../web-client + restart: unless-stopped expose: - "8080" networks: diff --git a/infra/id_rsa.pub b/infra/id_rsa.pub index b2c38ed..96c2a73 100644 --- a/infra/id_rsa.pub +++ b/infra/id_rsa.pub @@ -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 diff --git a/infra/inventory.ini b/infra/inventory.ini index a13054e..6010977 100644 --- a/infra/inventory.ini +++ b/infra/inventory.ini @@ -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 diff --git a/infra/main.tf b/infra/main.tf index f5a2ec1..01ba559 100644 --- a/infra/main.tf +++ b/infra/main.tf @@ -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" { @@ -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 @@ -92,7 +83,10 @@ 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] } } @@ -100,7 +94,3 @@ 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 -}