Skip to content

Commit e26f4a5

Browse files
committed
deploy stage added
1 parent 78b2586 commit e26f4a5

5 files changed

Lines changed: 244 additions & 52 deletions

File tree

.github/workflows/devsecops-pipeline.yml

Lines changed: 74 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -338,56 +338,78 @@ jobs:
338338
if: always()
339339
run: docker stop solar-system-app && docker rm solar-system-app
340340

341-
# Stage 11: Deploy to Azure VM
342-
# deploy-azure:
343-
# name: Deploy to Azure VM
344-
# runs-on: ubuntu-latest
345-
# needs: [container-scan, dast-zap]
346-
# if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
347-
# permissions:
348-
# contents: read
349-
# packages: read
350-
# environment:
351-
# name: production
352-
# url: http://${{ secrets.AZURE_VM_IP }}:3000
341+
# Stage 11: Deploy Infrastructure and Application to Azure
342+
deploy-azure:
343+
name: Deploy to Azure Web App
344+
runs-on: ubuntu-latest
345+
needs: [container-scan, dast-zap]
346+
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master'
347+
permissions:
348+
contents: read
349+
packages: read
350+
environment:
351+
name: production
352+
url: https://${{ secrets.AZURE_WEBAPP_NAME }}.azurewebsites.net
353353

354-
# steps:
355-
# - name: Deploy on Azure VM
356-
# uses: appleboy/ssh-action@master
357-
# with:
358-
# host: ${{ secrets.AZURE_VM_IP }}
359-
# username: ${{ secrets.AZURE_VM_USERNAME }}
360-
# key: ${{ secrets.AZURE_VM_SSH_KEY }}
361-
# script: |
362-
# # Log in to GitHub Container Registry
363-
# echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
364-
365-
# # Pull the latest image
366-
# docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
367-
368-
# # Stop and remove old container if exists
369-
# docker stop solar-system || true
370-
# docker rm solar-system || true
371-
372-
# # Run new container
373-
# docker run -d --name solar-system \
374-
# -p 3000:3000 \
375-
# --restart unless-stopped \
376-
# -e MONGO_URI="${{ secrets.MONGO_URI }}" \
377-
# ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.ref_name }}-${{ github.sha }}
378-
379-
# # Clean up old images (keep last 3)
380-
# docker image prune -af --filter "until=72h"
381-
382-
# # Verify deployment
383-
# sleep 5
384-
# curl -f http://localhost:3000/ready || exit 1
385-
386-
# echo "Deployment successful!"
387-
# docker ps | grep solar-system
388-
389-
# - name: Health check
390-
# run: |
391-
# sleep 10
392-
# curl -f http://${{ secrets.AZURE_VM_IP }}:3000/ready || exit 1
393-
# echo "Application is healthy and running!"
354+
steps:
355+
- name: Checkout code
356+
uses: actions/checkout@v4
357+
358+
- name: Log in to Azure
359+
uses: azure/login@v1
360+
with:
361+
creds: ${{ secrets.AZURE_CREDENTIALS }}
362+
363+
- name: Setup Terraform
364+
uses: hashicorp/setup-terraform@v3
365+
with:
366+
terraform_version: 1.6.0
367+
368+
- name: Terraform Init
369+
run: terraform init
370+
working-directory: ./terraform
371+
372+
- name: Terraform Plan
373+
run: |
374+
terraform plan \
375+
-var="app_name=${{ secrets.AZURE_WEBAPP_NAME }}" \
376+
-var="github_username=${{ github.repository_owner }}" \
377+
-var="github_token=${{ secrets.GITHUB_TOKEN }}" \
378+
-var="mongo_uri=${{ secrets.MONGO_URI }}" \
379+
-var="docker_image=${{ github.repository_owner }}/solar-system:${{ github.ref_name }}" \
380+
-out=tfplan
381+
working-directory: ./terraform
382+
env:
383+
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
384+
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
385+
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
386+
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
387+
388+
- name: Terraform Apply
389+
run: terraform apply -auto-approve tfplan
390+
working-directory: ./terraform
391+
env:
392+
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
393+
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
394+
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
395+
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
396+
397+
- name: Get Web App URL
398+
id: webapp
399+
run: |
400+
WEBAPP_URL=$(terraform output -raw webapp_url)
401+
echo "url=$WEBAPP_URL" >> $GITHUB_OUTPUT
402+
working-directory: ./terraform
403+
404+
- name: Restart Web App (force pull latest image)
405+
run: |
406+
az webapp restart \
407+
--name ${{ secrets.AZURE_WEBAPP_NAME }} \
408+
--resource-group $(terraform output -raw resource_group_name)
409+
working-directory: ./terraform
410+
411+
- name: Health check
412+
run: |
413+
sleep 60
414+
curl -f ${{ steps.webapp.outputs.url }}/ready || exit 1
415+
echo "Application is healthy and running on Azure Web App!"

terraform/backend.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
terraform {
2+
backend "azurerm" {
3+
resource_group_name = "tfstate"
4+
storage_account_name = "devtfstate12345"
5+
container_name = "tfstate"
6+
key = "solar-system.terraform.tfstate"
7+
}
8+
}

terraform/main.tf

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
azurerm = {
6+
source = "hashicorp/azurerm"
7+
version = "~> 3.0"
8+
}
9+
}
10+
}
11+
12+
provider "azurerm" {
13+
features {}
14+
}
15+
16+
# Resource Group
17+
resource "azurerm_resource_group" "rg" {
18+
name = var.resource_group_name
19+
location = var.location
20+
21+
tags = {
22+
Environment = "Production"
23+
Project = "SSD-DevSecOps"
24+
ManagedBy = "Terraform"
25+
}
26+
}
27+
28+
# App Service Plan (Linux-based for containers)
29+
resource "azurerm_service_plan" "app_plan" {
30+
name = var.app_service_plan_name
31+
location = azurerm_resource_group.rg.location
32+
resource_group_name = azurerm_resource_group.rg.name
33+
os_type = "Linux"
34+
sku_name = var.sku_name
35+
36+
tags = {
37+
Environment = "Production"
38+
Project = "SSD-DevSecOps"
39+
}
40+
}
41+
42+
# Linux Web App for Containers
43+
resource "azurerm_linux_web_app" "webapp" {
44+
name = var.app_name
45+
location = azurerm_resource_group.rg.location
46+
resource_group_name = azurerm_resource_group.rg.name
47+
service_plan_id = azurerm_service_plan.app_plan.id
48+
49+
site_config {
50+
always_on = var.sku_name != "F1" ? true : false # Free tier doesn't support always_on
51+
52+
application_stack {
53+
docker_registry_url = "https://ghcr.io"
54+
docker_image_name = var.docker_image
55+
docker_registry_username = var.github_username
56+
docker_registry_password = var.github_token
57+
}
58+
59+
health_check_path = "/ready"
60+
}
61+
62+
app_settings = {
63+
"MONGO_URI" = var.mongo_uri
64+
"WEBSITES_PORT" = "3000"
65+
"DOCKER_ENABLE_CI" = "true"
66+
"DOCKER_REGISTRY_SERVER_URL" = "https://ghcr.io"
67+
"DOCKER_REGISTRY_SERVER_USERNAME" = var.github_username
68+
"DOCKER_REGISTRY_SERVER_PASSWORD" = var.github_token
69+
}
70+
71+
https_only = true
72+
73+
logs {
74+
http_logs {
75+
file_system {
76+
retention_in_days = 7
77+
retention_in_mb = 35
78+
}
79+
}
80+
81+
application_logs {
82+
file_system_level = "Information"
83+
}
84+
}
85+
86+
tags = {
87+
Environment = "Production"
88+
Project = "SSD-DevSecOps"
89+
}
90+
}

terraform/outputs.tf

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
output "webapp_url" {
2+
description = "URL of the deployed Web App"
3+
value = "https://${azurerm_linux_web_app.webapp.default_hostname}"
4+
}
5+
6+
output "webapp_name" {
7+
description = "Name of the Web App"
8+
value = azurerm_linux_web_app.webapp.name
9+
}
10+
11+
output "resource_group_name" {
12+
description = "Name of the Resource Group"
13+
value = azurerm_resource_group.rg.name
14+
}
15+
16+
output "webapp_id" {
17+
description = "Resource ID of the Web App"
18+
value = azurerm_linux_web_app.webapp.id
19+
}

terraform/variables.tf

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
variable "resource_group_name" {
2+
description = "Name of the Azure Resource Group"
3+
type = string
4+
default = "rg-solar-system"
5+
}
6+
7+
variable "location" {
8+
description = "Azure region for resources"
9+
type = string
10+
default = "East US"
11+
}
12+
13+
variable "app_service_plan_name" {
14+
description = "Name of the App Service Plan"
15+
type = string
16+
default = "plan-solar-system"
17+
}
18+
19+
variable "app_name" {
20+
description = "Name of the Web App (must be globally unique)"
21+
type = string
22+
default = "solar-system-devsecops"
23+
}
24+
25+
variable "sku_name" {
26+
description = "SKU name for App Service Plan (F1=Free, B1=Basic, S1=Standard, P1V2=Premium)"
27+
type = string
28+
default = "B1" # Basic tier - cheapest paid tier with always_on support
29+
}
30+
31+
variable "docker_image" {
32+
description = "Docker image name with tag from GitHub Container Registry"
33+
type = string
34+
default = "deviant101/solar-system:latest"
35+
}
36+
37+
variable "github_username" {
38+
description = "GitHub username for container registry authentication"
39+
type = string
40+
sensitive = true
41+
}
42+
43+
variable "github_token" {
44+
description = "GitHub Personal Access Token for pulling images from GHCR"
45+
type = string
46+
sensitive = true
47+
}
48+
49+
variable "mongo_uri" {
50+
description = "MongoDB connection string"
51+
type = string
52+
sensitive = true
53+
}

0 commit comments

Comments
 (0)