Skip to content

Commit

Permalink
AZD can use containers instead of code for deployment (Azure-Samples#505
Browse files Browse the repository at this point in the history
)

* Add adminweb docker image

* Build off my branch

* Create Requirements.

* Seperate process

* Dont traverse paths

* Use poetry in docker build

* dont install the root app

* Add frontend image

* Revert docker changes

* Install the root again

* Use requirements rather than toml

* Use Requirements for images again

* Rename docker images

* Frontend use app directory

* Add python path

* Copy the whole code folder

* Frontend runs locally

* Frontend and Admin working

* Use containers as default hosting model

* Dont build from my branch anymore

* Only build docker images - after test

* Change trigger workflow

* Only run when tests succeed

* Trigger builds only if tests pass

* Only changed App Service names

* Reverting
  • Loading branch information
ross-p-smith authored Mar 25, 2024
1 parent 7b659e1 commit 90cb346
Show file tree
Hide file tree
Showing 16 changed files with 5,036 additions and 1,860 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/build-docker-admin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Admin Web App Docker Image

on:
workflow_run:
workflows: [Tests]
types: [completed]
branches: [main]

jobs:
docker-build-admin:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:

- name: Docker Login
uses: docker/login-action@v3
with:
registry: fruoccopublic.azurecr.io
username: fruoccopublic
password: ${{ secrets.DOCKER_PASSWORD }}

- uses: actions/checkout@v4

- name: Build the Admin Docker image
run:
docker pull fruoccopublic.azurecr.io/rag-adminwebapp:latest || true;
docker build . --file docker/Admin.Dockerfile --cache-from fruoccopublic.azurecr.io/rag-adminwebapp:latest --tag fruoccopublic.azurecr.io/rag-adminwebapp:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER;
docker tag fruoccopublic.azurecr.io/rag-adminwebapp:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER fruoccopublic.azurecr.io/rag-adminwebapp:latest;
docker push fruoccopublic.azurecr.io/rag-adminwebapp:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER;
docker push fruoccopublic.azurecr.io/rag-adminwebapp:latest;
30 changes: 30 additions & 0 deletions .github/workflows/build-docker-backend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Backend Docker Image

on:
workflow_run:
workflows: [Tests]
types: [completed]
branches: [main]

jobs:
docker-build-backend:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:

- name: Docker Login
uses: docker/login-action@v3
with:
registry: fruoccopublic.azurecr.io
username: fruoccopublic
password: ${{ secrets.DOCKER_PASSWORD }}

- uses: actions/checkout@v4

- name: Build the Backend Docker image
run:
docker pull fruoccopublic.azurecr.io/rag-backend:latest || true;
docker build . --file docker/Backend.Dockerfile --cache-from fruoccopublic.azurecr.io/rag-backend:latest --tag fruoccopublic.azurecr.io/rag-backend:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER;
docker tag fruoccopublic.azurecr.io/rag-backend:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER fruoccopublic.azurecr.io/rag-backend:latest;
docker push fruoccopublic.azurecr.io/rag-backend:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER;
docker push fruoccopublic.azurecr.io/rag-backend:latest;
30 changes: 30 additions & 0 deletions .github/workflows/build-docker-frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: WebApp Docker Image

on:
workflow_run:
workflows: [Tests]
types: [completed]
branches: [main]

jobs:
docker-build-frontend:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:

- name: Docker Login
uses: docker/login-action@v3
with:
registry: fruoccopublic.azurecr.io
username: fruoccopublic
password: ${{ secrets.DOCKER_PASSWORD }}

- uses: actions/checkout@v4

- name: Build the Frontend Docker image
run:
docker pull fruoccopublic.azurecr.io/rag-webapp:latest || true;
docker build . --file docker/Frontend.Dockerfile --cache-from fruoccopublic.azurecr.io/rag-webapp:latest --tag fruoccopublic.azurecr.io/rag-webapp:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER;
docker tag fruoccopublic.azurecr.io/rag-webapp:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER fruoccopublic.azurecr.io/rag-webapp:latest;
docker push fruoccopublic.azurecr.io/rag-webapp:$(date +'%Y-%m-%d')_$GITHUB_RUN_NUMBER;
docker push fruoccopublic.azurecr.io/rag-webapp:latest;
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ The easiest way to run this accelerator is in a VS Code Dev Containers, which wi
[![Open in Dev Containers](https://img.shields.io/static/v1?style=for-the-badge&label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/azure-samples/chat-with-your-data-solution-accelerator)
1. In the VS Code window that opens, once the project files show up (this may take several minutes), open a terminal window
1. Run `azd auth login`
1. Run `azd env set AZURE_APP_SERVICE_HOSTING_MODEL code` - This sets your environment to depoloy code rather than rely on public containers.
1. Run `azd up` - This will provision Azure resources and deploy the accelerator to those resources.

* **Important**: Beware that the resources created by this command will incur immediate costs, primarily from the AI Search resource. These resources may accrue costs even if you interrupt the command before it is fully executed. You can run `azd down` or delete the resources manually to avoid unnecessary spending.
Expand Down
12 changes: 12 additions & 0 deletions docker/Admin.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.11.7-bookworm
RUN apt-get update && apt-get install python3-tk tk-dev -y
COPY pyproject.toml /usr/local/src/myscripts/pyproject.toml
COPY poetry.lock /usr/local/src/myscripts/poetry.lock
WORKDIR /usr/local/src/myscripts/
RUN pip install --upgrade pip && pip install poetry && poetry export -o requirements.txt && pip install -r requirements.txt
COPY ./code/backend /usr/local/src/myscripts/admin
COPY ./code/backend/batch/utilities /usr/local/src/myscripts/utilities
WORKDIR /usr/local/src/myscripts/admin
ENV PYTHONPATH "${PYTHONPATH}:/usr/local/src/myscripts/"
EXPOSE 80
CMD ["streamlit", "run", "Admin.py", "--server.port", "80", "--server.enableXsrfProtection", "false"]
12 changes: 12 additions & 0 deletions docker/Backend.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM mcr.microsoft.com/azure-functions/python:4-python3.11

ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
AzureFunctionsJobHost__Logging__Console__IsEnabled=true \
AzureWebJobsFeatureFlags=EnableWorkerIndexing

COPY pyproject.toml /
COPY poetry.lock /
RUN pip install --upgrade pip && pip install poetry && poetry export -o requirements.txt && pip install -r requirements.txt

COPY ./code/backend/batch/utilities /home/site/wwwroot/utilities
COPY ./code/backend/batch /home/site/wwwroot
23 changes: 23 additions & 0 deletions docker/Frontend.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM node:20-alpine AS frontend
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
WORKDIR /home/node/app
COPY ./code/frontend/package*.json ./
USER node
RUN npm ci
COPY --chown=node:node ./code/frontend ./frontend
WORKDIR /home/node/app/frontend
RUN npm run build

FROM python:3.11.7-bookworm
RUN apt-get update && apt-get install python3-tk tk-dev -y

COPY pyproject.toml /usr/src/app/pyproject.toml
COPY poetry.lock /usr/src/app/poetry.lock
WORKDIR /usr/src/app
RUN pip install --upgrade pip && pip install poetry uwsgi && poetry export -o requirements.txt && pip install -r requirements.txt

COPY ./code/ /usr/src/app
COPY --from=frontend /home/node/app/static /usr/src/app/static/
ENV PYTHONPATH "${PYTHONPATH}:/usr/src/app"
EXPOSE 80
CMD ["uwsgi", "--http", ":80", "--wsgi-file", "app.py", "--callable", "app", "-b","32768"]
68 changes: 60 additions & 8 deletions infra/app/adminweb.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,101 @@ param contentSafetyName string = ''
param allowedOrigins array = []
param appServicePlanId string
param appCommandLine string = 'python -m streamlit run Admin.py --server.port 8000 --server.address 0.0.0.0 --server.enableXsrfProtection false'
param runtimeName string = 'python'
param runtimeVersion string = ''
param applicationInsightsName string = ''
param keyVaultName string = ''
param azureOpenAIName string = ''
param azureAISearchName string = ''
param speechServiceName string = ''
@secure()
param appSettings object = {}
param serviceName string = 'adminweb'
param useKeyVault bool
param openAIKeyName string = ''
param storageAccountKeyName string = ''
param formRecognizerKeyName string = ''
param searchKeyName string = ''
param contentSafetyKeyName string = ''
param speechKeyName string = ''
param keyVaultEndpoint string = ''
param authType string
param dockerFullImageName string = ''
param useDocker bool = dockerFullImageName != ''

module adminweb '../core/host/appservice.bicep' = {
name: '${name}-app-module'
params: {
name: name
location: location
tags: union(tags, { 'azd-service-name': serviceName })
tags: tags
allowedOrigins: allowedOrigins
appCommandLine: appCommandLine
appCommandLine: useDocker ? '' : appCommandLine
runtimeName: runtimeName
runtimeVersion: runtimeVersion
keyVaultName: keyVaultName
dockerFullImageName: dockerFullImageName
scmDoBuildDuringDeployment: useDocker ? false : true
applicationInsightsName: applicationInsightsName
appServicePlanId: appServicePlanId
appSettings: union(appSettings, {
AZURE_AUTH_TYPE: authType
USE_KEY_VAULT: useKeyVault ? useKeyVault : ''
AZURE_KEY_VAULT_ENDPOINT: useKeyVault ? keyVaultEndpoint : ''
AZURE_OPENAI_API_KEY: useKeyVault ? openAIKeyName : listKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.CognitiveServices/accounts', azureOpenAIName), '2023-05-01').key1
AZURE_SEARCH_KEY: useKeyVault ? searchKeyName : listAdminKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.Search/searchServices', azureAISearchName), '2021-04-01-preview').primaryKey
AZURE_BLOB_ACCOUNT_KEY: useKeyVault ? storageAccountKeyName : listKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.Storage/storageAccounts', storageAccountName), '2021-09-01').keys[0].value
AZURE_FORM_RECOGNIZER_KEY: useKeyVault ? formRecognizerKeyName : listKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.CognitiveServices/accounts', formRecognizerName), '2023-05-01').key1
AZURE_CONTENT_SAFETY_KEY: useKeyVault ? contentSafetyKeyName : listKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.CognitiveServices/accounts', contentSafetyName), '2023-05-01').key1
AZURE_SPEECH_SERVICE_KEY: useKeyVault ? speechKeyName : listKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.CognitiveServices/accounts', speechServiceName), '2023-05-01').key1
})
}
}

// Storage Blob Data Contributor
module storageRoleBackend '../core/security/role.bicep' = if (authType == 'rbac') {
name: 'storage-role-backend'
params: {
principalId: adminweb.outputs.identityPrincipalId
roleDefinitionId: 'ba92f5b4-2d11-453d-a403-e96b0029c9fe'
principalType: 'ServicePrincipal'
}
}

// Cognitive Services User
module openAIRoleBackend '../core/security/role.bicep' = if (authType == 'rbac') {
name: 'openai-role-backend'
params: {
principalId: adminweb.outputs.identityPrincipalId
roleDefinitionId: 'a97b65f3-24c7-4388-baec-2e87135dc908'
principalType: 'ServicePrincipal'
}
}

// Contributor
// This role is used to grant the service principal contributor access to the resource group
// See if this is needed in the future.
module openAIRoleBackendContributor '../core/security/role.bicep' = if (authType == 'rbac') {
name: 'openai-role-backend-contributor'
params: {
principalId: adminweb.outputs.identityPrincipalId
roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c'
principalType: 'ServicePrincipal'
}
}

// Search Index Data Contributor
module searchRoleBackend '../core/security/role.bicep' = if (authType == 'rbac') {
name: 'search-role-backend'
params: {
principalId: adminweb.outputs.identityPrincipalId
roleDefinitionId: '8ebe5a00-799e-43f5-93ac-243d3dce84a7'
principalType: 'ServicePrincipal'
}
}

module adminwebaccess '../core/security/keyvault-access.bicep' = if (useKeyVault) {
name: 'adminweb-keyvault-access'
params: {
keyVaultName: keyVaultName
runtimeName: 'python'
runtimeVersion: '3.11'
scmDoBuildDuringDeployment: true
principalId: adminweb.outputs.identityPrincipalId
}
}

Expand Down
52 changes: 47 additions & 5 deletions infra/app/function.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ param storageAccountName string = ''
param tags object = {}
@secure()
param appSettings object = {}
param serviceName string = 'function'
param applicationInsightsName string = ''
param runtimeName string = 'python'
param runtimeVersion string = '3.11'
param runtimeVersion string = ''
@secure()
param clientKey string
param keyVaultName string = ''
Expand All @@ -23,24 +23,26 @@ param formRecognizerKeyName string = ''
param searchKeyName string = ''
param contentSafetyKeyName string = ''
param speechKeyName string = ''
param keyVaultEndpoint string = ''
param authType string
param dockerFullImageName string = ''

module function '../core/host/functions.bicep' = {
name: '${name}-app-module'
params: {
name: name
location: location
tags: union(tags, { 'azd-service-name': serviceName })
tags: tags
appServicePlanId: appServicePlanId
applicationInsightsName: applicationInsightsName
storageAccountName: storageAccountName
keyVaultName: keyVaultName
runtimeName: runtimeName
runtimeVersion: runtimeVersion
dockerFullImageName: dockerFullImageName
appSettings: union(appSettings, {
WEBSITES_ENABLE_APP_SERVICE_STORAGE: 'false'
AZURE_AUTH_TYPE: authType
USE_KEY_VAULT: useKeyVault ? useKeyVault : ''
AZURE_KEY_VAULT_ENDPOINT: useKeyVault ? keyVaultEndpoint : ''
AZURE_OPENAI_API_KEY: useKeyVault ? openAIKeyName : listKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.CognitiveServices/accounts', azureOpenAIName), '2023-05-01').key1
AZURE_SEARCH_KEY: useKeyVault ? searchKeyName : listAdminKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.Search/searchServices', azureAISearchName), '2021-04-01-preview').primaryKey
AZURE_BLOB_ACCOUNT_KEY: useKeyVault ? storageAccountKeyName : listKeys(resourceId(subscription().subscriptionId, resourceGroup().name, 'Microsoft.Storage/storageAccounts', storageAccountName), '2021-09-01').keys[0].value
Expand Down Expand Up @@ -78,4 +80,44 @@ resource waitFunctionDeploymentSection 'Microsoft.Resources/deploymentScripts@20
]
}

// Cognitive Services User
module openAIRoleFunction '../core/security/role.bicep' = if (authType == 'rbac') {
name: 'openai-role-function'
params: {
principalId: function.outputs.identityPrincipalId
roleDefinitionId: 'a97b65f3-24c7-4388-baec-2e87135dc908'
principalType: 'ServicePrincipal'
}
}

// Contributor
// This role is used to grant the service principal contributor access to the resource group
// See if this is needed in the future.
module openAIRoleFunctionContributor '../core/security/role.bicep' = if (authType == 'rbac') {
name: 'openai-role-function-contributor'
params: {
principalId: function.outputs.identityPrincipalId
roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c'
principalType: 'ServicePrincipal'
}
}

// Search Index Data Contributor
module searchRoleFunction '../core/security/role.bicep' = if (authType == 'rbac') {
name: 'search-role-function'
params: {
principalId: function.outputs.identityPrincipalId
roleDefinitionId: '8ebe5a00-799e-43f5-93ac-243d3dce84a7'
principalType: 'ServicePrincipal'
}
}

module functionaccess '../core/security/keyvault-access.bicep' = if (useKeyVault) {
name: 'function-keyvault-access'
params: {
keyVaultName: keyVaultName
principalId: function.outputs.identityPrincipalId
}
}

output FUNCTION_IDENTITY_PRINCIPAL_ID string = function.outputs.identityPrincipalId
Loading

0 comments on commit 90cb346

Please sign in to comment.