diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..ea7f2ce5e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git* +**/*.pyc +.venv/ \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..0642b64d2 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,90 @@ +name: Build and Deploy Container App to Azure + +on: + push: + branches: + - main # Trigger on push to the main branch + +env: + RESOURCE_GROUP: BCSAI2024-DEVOPS-STUDENTS-A-DEV + REGISTRY_NAME: dmoneycontainerregistry + IMAGE_BASE_NAME: dmoneyimage + WEB_APP_NAME: dmoneyWebApp + LOCATION: westeurope + +jobs: + build: + runs-on: ubuntu-latest + steps: + # Step 1: Checkout the repository + - name: Checkout code + uses: actions/checkout@v3 + + # Step 2: Log in to Azure + - name: Log in to Azure + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + # Step 3: Deploy infrastructure using Bicep + - name: Deploy infrastructure using Bicep + run: | + az deployment group create \ + --resource-group ${{ env.RESOURCE_GROUP }} \ + --template-file main.bicep \ + --parameters dmoneyContainerRegistryName=${{ env.REGISTRY_NAME }} \ + dmoneyAppServicePlanName=dmoneyAppServicePlan \ + dmoneyWebAppName=${{ env.WEB_APP_NAME }} \ + keyVaultName=dmoneyKeyVault + location=${{ env.LOCATION }} + + # Step 4: Fetch ACR credentials dynamically + - name: Fetch ACR credentials + id: acr-credentials + run: | + echo "Fetching ACR credentials..." + echo "::set-output name=login-server::$(az acr show --name ${{ env.REGISTRY_NAME }} --query "loginServer" -o tsv)" + echo "::set-output name=username::$(az acr credential show --name ${{ env.REGISTRY_NAME }} --query "username" -o tsv)" + echo "::set-output name=password::$(az acr credential show --name ${{ env.REGISTRY_NAME }} --query "passwords[0].value" -o tsv)" + + # Step 5: Log in to Azure Container Registry + - name: Log in to Azure Container Registry + uses: azure/docker-login@v1 + with: + login-server: ${{ steps.acr-credentials.outputs.login-server }} + username: ${{ steps.acr-credentials.outputs.username }} + password: ${{ steps.acr-credentials.outputs.password }} + + # Step 6: Set image version + - name: Set image version + id: image-version + run: echo "::set-output name=version::$(echo ${GITHUB_REF#refs/heads/})-$(date +'%Y.%m.%d.%H.%M')" + + # Step 7: Build and push Docker image + - name: Build and push image + run: | + docker build . -t ${{ steps.acr-credentials.outputs.login-server }}/${{ env.IMAGE_BASE_NAME }}:${{ steps.image-version.outputs.version }} + docker build . -t ${{ steps.acr-credentials.outputs.login-server }}/${{ env.IMAGE_BASE_NAME }}:${{ github.ref_name }}-latest + docker push ${{ steps.acr-credentials.outputs.login-server }}/${{ env.IMAGE_BASE_NAME }}:${{ steps.image-version.outputs.version }} + docker push ${{ steps.acr-credentials.outputs.login-server }}/${{ env.IMAGE_BASE_NAME }}:${{ github.ref_name }}-latest + + outputs: + image: ${{ steps.acr-credentials.outputs.login-server }}/${{ env.IMAGE_BASE_NAME }}:${{ steps.image-version.outputs.version }} + + deploy: + runs-on: ubuntu-latest + needs: build # This ensures the deploy job runs after build + + steps: + # Step 1: Log in to Azure + - name: Log in to Azure + uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + # Step 2: Deploy Docker image to Azure Web App + - name: Deploy to Azure Web App + uses: azure/webapps-deploy@v3 + with: + app-name: ${{ env.WEB_APP_NAME }} + images: ${{ needs.build.outputs.image }} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..f9940586c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 + +FROM python:3.11 + +WORKDIR /code + +COPY requirements.txt . + +RUN pip3 install -r requirements.txt + +COPY . . + +EXPOSE 50505 + +ENTRYPOINT ["gunicorn", "app:app"] diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 000000000..fea6a15e4 --- /dev/null +++ b/gunicorn.conf.py @@ -0,0 +1,14 @@ +# Gunicorn configuration file +import multiprocessing + +max_requests = 1000 +max_requests_jitter = 50 + +log_file = "-" + +bind = "0.0.0.0:50505" + +workers = (multiprocessing.cpu_count() * 2) + 1 +threads = workers + +timeout = 120 \ No newline at end of file diff --git a/main.bicep b/main.bicep new file mode 100644 index 000000000..edf9ab867 --- /dev/null +++ b/main.bicep @@ -0,0 +1,80 @@ +// Parameters +param dmoneyContainerRegistryName string = 'dmoneycontainerregistry' // Container Registry Name +param dmoneyAppServicePlanName string = 'dmoneyAppServicePlan' // App Service Plan Name +param location string = 'westeurope' // Desired Azure Region +param dmoneyWebAppName string = 'dmoneyWebApp' // Web App Name +param keyVaultName string = 'dmoneyKeyVault' // Key Vault Name +// Azure Container Registry Module +module containerRegistry 'modules/containerRegistry.bicep' = { + name: 'deployContainerRegistry' + params: { + dmoneyContainerRegistryName: dmoneyContainerRegistryName + location: location + } +} + +// Azure App Service Plan Module +module dmoneyAppServicePlan 'modules/appServicePlan.bicep' = { + name: 'deployAppServicePlan' + params: { + dmoneyAppServicePlanName: dmoneyAppServicePlanName + location: location + sku: { + capacity: 1 + family: 'B' + name: 'B1' + size: 'B1' + tier: 'Basic' + } + kind: 'Linux' + reserved: true + } +} + +// Pass appSettings as an array +module webApp 'modules/webApp.bicep' = { + name: 'deployWebApp' + params: { + name: dmoneyWebAppName + location: location + kind: 'app' + serverFarmResourceId: dmoneyAppServicePlan.outputs.id + siteConfig: { + linuxFxVersion: 'DOCKER|${containerRegistry.outputs.loginServer}/dmoneyimage:latest' + appCommandLine: '' + } + appSettingsArray: [ + { + name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE' + value: 'false' + } + { + name: 'DOCKER_REGISTRY_SERVER_URL' + value: containerRegistry.outputs.loginServer + } + { + name: 'DOCKER_REGISTRY_SERVER_USERNAME' + value: containerRegistry.outputs.username + } + { + name: 'DOCKER_REGISTRY_SERVER_PASSWORD' + value: containerRegistry.outputs.password + } + ] + } +} +resource keyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' = { + name: keyVaultName + location: location + properties: { + tenantId: subscription().tenantId + sku: { + family: 'A' + name: 'standard' + } + accessPolicies: [] // Explicitly set to an empty array + } +} + +// Output for verification or integration +output keyVaultUri string = keyVault.properties.vaultUri diff --git a/main.parameters.json b/main.parameters.json new file mode 100644 index 000000000..31469a1a7 --- /dev/null +++ b/main.parameters.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2020-10-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "enviromentType": { + "value": "dev" + }, + "dmoneycontainerRegistryName": { + "value": "dmoneycontainerregistry" + }, + "containerRegistryImageName": { + "value": "dmoneyimage" + }, + "containerRegistryImageVersion": { + "value": "v1" + }, + "appServicePlanName": { + "value": "dmoneyAppServicePlan" + }, + "webAppName": { + "value": "dmoneyWebApp" + }, + "location": { + "value": "westeurope" + }, + "keyVaultName": { + "value": "dmoneyKeyVault" + } + + } +} diff --git a/modules/appServicePlan.bicep b/modules/appServicePlan.bicep new file mode 100644 index 000000000..4d44919f3 --- /dev/null +++ b/modules/appServicePlan.bicep @@ -0,0 +1,17 @@ +param dmoneyAppServicePlanName string +param location string +param sku object +param kind string +param reserved bool + +resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = { + name: dmoneyAppServicePlanName + location: location + sku: sku + kind: kind + properties: { + reserved: reserved + } +} + +output id string = appServicePlan.id \ No newline at end of file diff --git a/modules/containerRegistry.bicep b/modules/containerRegistry.bicep new file mode 100644 index 000000000..9ed32547b --- /dev/null +++ b/modules/containerRegistry.bicep @@ -0,0 +1,17 @@ +param dmoneyContainerRegistryName string +param location string + +resource containerRegistry 'Microsoft.ContainerRegistry/registries@2021-12-01-preview' = { + name: dmoneyContainerRegistryName + location: location + sku: { + name: 'Basic' + } + properties: { + adminUserEnabled: true + } +} + +output loginServer string = containerRegistry.properties.loginServer +output username string = listCredentials(containerRegistry.id, '2021-12-01-preview').username +output password string = listCredentials(containerRegistry.id, '2021-12-01-preview').passwords[0].value \ No newline at end of file diff --git a/modules/key-vault.bicep b/modules/key-vault.bicep new file mode 100644 index 000000000..141c0de0d --- /dev/null +++ b/modules/key-vault.bicep @@ -0,0 +1,29 @@ +param location string +param name string +param enableVaultForDeployment bool = true +param roleAssignments array + +resource keyVault 'Microsoft.KeyVault/vaults@2021-10-01' = { + name: name + location: location + properties: { + enabledForDeployment: enableVaultForDeployment + enabledForTemplateDeployment: true + sku: { + family: 'A' + name: 'standard' + } + } +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for role in roleAssignments: { + name: guid(keyVault.id, role.principalId, role.roleDefinitionIdOrName) + properties: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') + principalId: role.principalId + principalType: role.principalType + scope: keyVault.id + } +}] + +output keyVaultUri string = keyVault.properties.vaultUri diff --git a/modules/webApp.bicep b/modules/webApp.bicep new file mode 100644 index 000000000..61d4df507 --- /dev/null +++ b/modules/webApp.bicep @@ -0,0 +1,22 @@ +param name string +param location string +param kind string +param serverFarmResourceId string +param siteConfig object +param appSettingsArray array // Accept the array + +resource webApp 'Microsoft.Web/sites@2022-03-01' = { + name: name + location: location + kind: kind + properties: { + serverFarmId: serverFarmResourceId + siteConfig: siteConfig + appSettings: appSettingsArray // Use the array directly + } + identity: { + type: 'SystemAssigned' + } +} + +output id string = webApp.id \ No newline at end of file