Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Web App Workflows #3

Merged
merged 16 commits into from
Aug 23, 2024
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
92 changes: 92 additions & 0 deletions .github/workflows/_webappDeployTemplate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Web App Deploy Template

on:
workflow_call:
inputs:
environment:
required: true
type: string
default: "dev"
description: "Specifies the environment of the deployment."
python_version:
required: true
type: string
default: "3.11"
description: "Specifies the python version."
webapp_directory:
required: true
type: string
description: "Specifies the directory of the Azure Web App."
webapp_name:
required: true
type: string
description: "Specifies the name of the Azure Web App."
tenant_id:
required: true
type: string
description: "Specifies the tenant id of the deployment."
subscription_id:
required: true
type: string
description: "Specifies the subscription id of the deployment."
secrets:
CLIENT_ID:
required: true
description: "Specifies the client id."
CLIENT_SECRET:
required: true
description: "Specifies the client secret."

jobs:
deployment:
name: Web App Deploy
runs-on: [self-hosted]
continue-on-error: false
environment: ${{ inputs.environment }}
concurrency:
group: webapp-${{ inputs.webapp_name }}-${{ inputs.environment }}
cancel-in-progress: false

steps:
# Check Out Repository
- name: Check Out Repository
id: checkout_repository
uses: actions/checkout@v4

# Setup Python
- name: Setup Python
id: python_setup
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python_version }}
cache: "pip"
cache-dependency-path: |
${{ inputs.webapp_directory }}/requirements.txt

# Install Function Dependencies
- name: Resolve Function Dependencies
id: function_dependencies
shell: bash
run: |
pushd "${WEBAPP_DIRECTORY}"
python -m pip install --upgrade pip
pip install -r requirements.txt --target=".python_packages/lib/site-packages"
popd
env:
WEBAPP_DIRECTORY: ${{ inputs.webapp_directory }}

# Login to Azure
- name: Azure Login
id: azure_login
uses: azure/login@v2
with:
creds: '{"clientId":"${{ secrets.CLIENT_ID }}","clientSecret":"${{ secrets.CLIENT_SECRET }}","subscriptionId":"${{ inputs.subscription_id }}","tenantId":"${{ inputs.tenant_id }}"}'

# Deploy Web App
- name: Deploy Web App
id: webapp_deploy
uses: Azure/webapps-deploy@v3
with:
app-name: ${{ inputs.webapp_name }}
package: ${{ inputs.webapp_directory }}
startup-command: "gunicorn --bind 0.0.0.0 --worker-class aiohttp.worker.GunicornWebWorker --timeout 600 app:APP"
47 changes: 47 additions & 0 deletions .github/workflows/_webappTestTemplate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Web App Test Template

on:
workflow_call:
inputs:
python_version:
required: true
type: string
default: "3.11"
description: "Specifies the python version."
webapp_directory:
required: true
type: string
description: "Specifies the directory of the Azure Web App."

jobs:
test:
name: Web App Test
runs-on: [ubuntu-latest]
continue-on-error: false

steps:
# Check Out Repository
- name: Check Out Repository
id: checkout_repository
uses: actions/checkout@v4

# Setup Python
- name: Setup Python
id: python_setup
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python_version }}
cache: "pip"
cache-dependency-path: |
${{ inputs.webapp_directory }}/requirements.txt
requirements.txt

# Run Python Tests
- name: Run Python Tests
id: python_test
run: |
pip install -r "${WEBAPP_DIRECTORY}/requirements.txt" -q
pip install -r requirements.txt -q
pytest
env:
WEBAPP_DIRECTORY: ${{ inputs.webapp_directory }}
42 changes: 42 additions & 0 deletions .github/workflows/webapp.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Function App Deployment
on:
push:
branches:
- main
paths:
- "**.py"
- "code/backend/**"
- "tests/**"
- "requirements.txt"

pull_request:
branches:
- main
paths:
- "**.py"
- "code/backend/**"
- "tests/**"
- "requirements.txt"

jobs:
webapp_test:
uses: ./.github/workflows/_webappTestTemplate.yml
name: "Web App Test"
with:
python_version: "3.11"
webapp_directory: "./code/backend"

webapp_dev:
uses: ./.github/workflows/_webappDeployTemplate.yml
name: "Web App - Dev"
needs: [webapp_test]
with:
environment: "dev"
python_version: "3.11"
webapp_directory: "./code/backend"
webapp_name: "assis-dev-app001"
tenant_id: "3556be79-2979-4b19-a1af-4dd4e6d9ed7e"
subscription_id: "8f171ff9-2b5b-4f0f-aed5-7fa360a1d094"
secrets:
CLIENT_ID: ${{ secrets.CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
5 changes: 3 additions & 2 deletions code/backend/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Settings(BaseSettings):
PROJECT_NAME: str = "BotAssistantSample"
SERVER_NAME: str = "BotAssistantSample"
APP_VERSION: str = "v0.0.1"
PORT: int = 3978
PORT: int = 8000

# Logging settings
LOGGING_LEVEL: int = logging.INFO
Expand All @@ -22,7 +22,8 @@ class Settings(BaseSettings):
APP_ID: str = Field(default="", alias="MICROSOFT_APP_ID")
APP_PASSWORD: str = Field(default="", alias="MICROSOFT_APP_PASSWORD")
APP_TENANTID: str = Field(default="", alias="MICROSOFT_APP_TENANTID")
APP_TYPE: str = Field(default="UserAssignedMSI", alias="MICROSOFT_APP_TYPE")
APP_TYPE: str = Field(default="", alias="MICROSOFT_APP_TYPE")
MANAGED_IDENTITY_CLIENT_ID: str

# Azure Open AI settings
AZURE_OPEN_AI_ENDPOINT: str
Expand Down
5 changes: 4 additions & 1 deletion code/backend/llm/assisstant.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ def __init__(
*args,
) -> None:
token_provider = get_bearer_token_provider(
DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
DefaultAzureCredential(
managed_identity_client_id=settings.MANAGED_IDENTITY_CLIENT_ID
),
"https://cognitiveservices.azure.com/.default",
)
self.client = AzureOpenAI(
api_version=settings.AZURE_OPEN_AI_API_VERSION,
Expand Down
8 changes: 4 additions & 4 deletions code/infra/aoai.tf
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ module "azure_open_ai" {
cognitive_account_outbound_network_access_restricted = true
cognitive_account_outbound_network_access_allowed_fqdns = []
cognitive_account_deployments = {}
diagnostics_configurations = []
subnet_id = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/tfmdltst-dev-rg/providers/Microsoft.Network/virtualNetworks/tfmdltst-dev-vnet/subnets/PrivateEndpoints"
diagnostics_configurations = local.diagnostics_configurations
subnet_id = azapi_resource.subnet_private_endpoints.id
connectivity_delay_in_seconds = 0
private_dns_zone_id_cognitive_account = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.cognitiveservices.azure.com"
customer_managed_key = null
private_dns_zone_id_cognitive_account = var.private_dns_zone_id_open_ai
customer_managed_key = local.customer_managed_key
}

resource "azurerm_cognitive_deployment" "cognitive_deployment_gpt_4o" {
Expand Down
2 changes: 1 addition & 1 deletion code/infra/botservice.tf
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module "bot_service" {
tags = var.tags
bot_service_name = "${local.prefix}-bot001"
bot_service_location = "global"
bot_service_endpoint = "https://${azurerm_linux_web_app.linux_web_app.default_hostname}"
bot_service_endpoint = "https://${azurerm_linux_web_app.linux_web_app.default_hostname}/api/messages"
bot_service_luis = {
app_ids = []
key = null
Expand Down
15 changes: 9 additions & 6 deletions code/infra/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ locals {
# Web app locals
app_settings_default = {
# Configuration app settings
SCM_DO_BUILD_DURING_DEPLOYMENT = "true"
WEBSITE_CONTENTOVERVNET = "1"
APPLICATIONINSIGHTS_CONNECTION_STRING = module.application_insights.application_insights_connection_string
ApplicationInsightsAgent_EXTENSION_VERSION = "~3"
SCM_DO_BUILD_DURING_DEPLOYMENT = "1"
WEBSITE_CONTENTOVERVNET = "1"

# Auth app settings
MICROSOFT_APP_ID = module.user_assigned_identity.user_assigned_identity_client_id
MICROSOFT_APP_PASSWORD = ""
MICROSOFT_APP_TENANTID = module.user_assigned_identity.user_assigned_identity_tenant_id
MICROSOFT_APP_TYPE = "UserAssignedMSI"
MICROSOFT_APP_ID = module.user_assigned_identity.user_assigned_identity_client_id
MICROSOFT_APP_PASSWORD = ""
MICROSOFT_APP_TENANTID = module.user_assigned_identity.user_assigned_identity_tenant_id
MICROSOFT_APP_TYPE = "UserAssignedMSI"
MANAGED_IDENTITY_CLIENT_ID = module.user_assigned_identity.user_assigned_identity_client_id

# Azure open ai app settings
AZURE_OPEN_AI_ENDPOINT = module.azure_open_ai.cognitive_account_endpoint
Expand Down
4 changes: 2 additions & 2 deletions code/infra/roleassignments_uai.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
resource "azurerm_role_assignment" "uai_roleassignment_open_ai_user" {
resource "azurerm_role_assignment" "uai_roleassignment_open_ai_contributor" {
description = "Required for accessing azure open ai from the web app"
scope = module.azure_open_ai.cognitive_account_id
role_definition_name = "Cognitive Services OpenAI User"
role_definition_name = "Cognitive Services OpenAI Contributor"
principal_id = module.user_assigned_identity.user_assigned_identity_principal_id
principal_type = "ServicePrincipal"
}
11 changes: 11 additions & 0 deletions code/infra/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,14 @@ variable "private_dns_zone_id_bot_framework_token" {
error_message = "Please specify a valid resource ID for the private DNS Zone."
}
}

variable "private_dns_zone_id_open_ai" {
description = "Specifies the resource ID of the private DNS zone for Azure Open AI. Not required if DNS A-records get created via Azure Policy."
type = string
sensitive = false
default = ""
validation {
condition = var.private_dns_zone_id_open_ai == "" || (length(split("/", var.private_dns_zone_id_open_ai)) == 9 && endswith(var.private_dns_zone_id_open_ai, "privatelink.openai.azure.com"))
error_message = "Please specify a valid resource ID for the private DNS Zone."
}
}
13 changes: 10 additions & 3 deletions code/infra/webapplinux.tf
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,29 @@ resource "azurerm_linux_web_app" "linux_web_app" {
always_on = true
api_definition_url = null
api_management_api_id = null
app_command_line = null
app_command_line = "gunicorn --bind 0.0.0.0 --worker-class aiohttp.worker.GunicornWebWorker --timeout 600 app:APP"
application_stack {
python_version = "3.11"
}
cors {
allowed_origins = [
"https://botservice.hosting.portal.azure.net",
"https://hosting.onecloud.azure-test.net",
]
support_credentials = true
}
container_registry_managed_identity_client_id = null
container_registry_use_managed_identity = null
ftps_state = "Disabled"
http2_enabled = true
ip_restriction_default_action = "Deny"
ip_restriction_default_action = "Allow" # "Deny"
load_balancing_mode = "LeastRequests"
local_mysql_enabled = false
managed_pipeline_mode = "Integrated"
minimum_tls_version = "1.2"
remote_debugging_enabled = false
remote_debugging_version = "VS2022"
scm_ip_restriction_default_action = "Deny"
scm_ip_restriction_default_action = "Allow" # "Deny"
scm_use_main_ip_restriction = false
scm_minimum_tls_version = "1.2"
use_32_bit_worker = false
Expand Down
1 change: 1 addition & 0 deletions config/PerfectThymeTech/vars.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ private_dns_zone_id_vault = "/subscriptions/8f171ff9-2b5b-4f0
private_dns_zone_id_sites = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.azurewebsites.net"
private_dns_zone_id_bot_framework_directline = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.directline.botframework.com"
private_dns_zone_id_bot_framework_token = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.token.botframework.com"
private_dns_zone_id_open_ai = "/subscriptions/8f171ff9-2b5b-4f0f-aed5-7fa360a1d094/resourceGroups/mycrp-prd-global-dns/providers/Microsoft.Network/privateDnsZones/privatelink.openai.azure.com"
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
pythonpath = code/backend
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pre-commit~=3.8.0
pytest~=8.3.2
13 changes: 13 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pytest


@pytest.mark.parametrize("version", ("v1",))
def test_get_heartbeat(version):
# arrange
path = f"/{version}/health/heartbeat"

# action
response = f"Do some work with {path}"

# assert
assert response == response
Loading