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

Feat azd compatible #9

Merged
merged 17 commits into from
Jan 2, 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
6 changes: 0 additions & 6 deletions .azure/config

This file was deleted.

1 change: 0 additions & 1 deletion .azure/config.json

This file was deleted.

6 changes: 0 additions & 6 deletions .azure/translation-telephone/.env

This file was deleted.

7 changes: 0 additions & 7 deletions .azure/translation-telephone/config.json

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ env
.coverage
node_modules
.venv
.azure
36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,36 @@ The app is currently hosted on Microsoft Azure. Specifically:
* Azure Database for PostGreSQL flexible server
* Azure Cognitive Services (for Translation)

To deploy your own instance, follow the [tutorial for Flask app + PostGreSQL deployment](https://docs.microsoft.com/en-us/azure/app-service/tutorial-python-postgresql-app) but using this app instead of the sample app.
To deploy your own instance, follow these steps:

Make sure you specify the following environment variables in the App Service configuration:
1. Sign up for a [free Azure account](https://azure.microsoft.com/free/?WT.mc_id=python-79461-pamelafox)
2. Install the [Azure Dev CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?WT.mc_id=python-79461-pamelafox). (If you open this repository in Codespaces or with the VS Code Dev Containers extension, that part will be done for you.)
3. Initialize a new `azd` environment:

* `DBHOST`, `DBNAME`, `DBPASS`, `DBUSER`: The above linked tutorial shows how to set these.
* `FLASK_APP`: Set to 'src'
* `AZURE_TRANSLATE_API_KEY`: Get this by registering for Azure Cognitive Services.
```shell
azd init
```

You will also need to migrate the database by using the App Service SSH and running `flask db upgrade`.
It will prompt you to provide a name (like "flask-app") that will later be used in the name of the deployed resources.

4. Provision and deploy all the resources:

```shell
azd up
```

It will prompt you to login, pick a subscription, and provide a location (like "eastus"). Then it will provision the resources in your account and deploy the latest code. If you get an error with deployment, changing the location (like to "centralus") can help, as there may be availability constraints for some of the resources.

5. When azd has finished deploying, you'll see an endpoint URI in the command output. Visit that URI and you should see the website and be able to translate messages.

6. For the website to work fully (i.e. save translations to the database), you must migrate the database. Navigate to the App Service in the Azure Portal, select SSH, and run this command once you're in the SSH terminal:

```shell
flask db upgrade
```

6. When you've made any changes to the app code, you can just run:

```shell
azd deploy
```
2 changes: 1 addition & 1 deletion azure.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json

name: flask-db-quiz-example
name: translation-telephone

services:
web:
Expand Down
15 changes: 15 additions & 0 deletions infra/app/security.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
param keyVaultName string
param rgName string

param name string
param cognitiveServiceName string

// Store Cognitive Service Key in Key Vault
module cognitiveServiceSecret '../core/security/keyvault-secret.bicep' = {
name: 'cognitiveServiceKey'
params: {
name: name
keyVaultName : keyVaultName
secretValue: listKeys(resourceId(subscription().subscriptionId, rgName, 'Microsoft.CognitiveServices/accounts', cognitiveServiceName), '2023-05-01').key1
}
}
41 changes: 41 additions & 0 deletions infra/core/ai/cognitiveservices.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@description('The name of the Cognitive Service.')
john0isaac marked this conversation as resolved.
Show resolved Hide resolved
param name string

@description('The location into which your Azure resources should be deployed.')
param location string = resourceGroup().location

@description('The tags to apply to each resource.')
param tags object = {}

@description('The kind of Cognitive Service to create. See: https://learn.microsoft.com/en-us/azure/cognitive-services/create-account-bicep for available kinds.')
@allowed([ 'CognitiveServices', 'ComputerVision', 'CustomVision.Prediction', 'CustomVision.Training', 'Face', 'FormRecognizer', 'SpeechServices', 'LUIS', 'QnAMaker', 'TextAnalytics', 'TextTranslation', 'AnomalyDetector', 'ContentModerator', 'Personalizer', 'OpenAI' ])
param kind string

@description('The name of the SKU. Be aware that not all SKUs may be available for your Subscription. See: https://learn.microsoft.com/en-us/rest/api/cognitiveservices/accountmanagement/resource-skus')
@allowed([ 'F0', 'S0', 'S1', 'S2', 'S3', 'S4' ])
param sku string

param publicNetworkAccess string = 'Enabled'

resource cognitiveService 'Microsoft.CognitiveServices/accounts@2023-05-01' = {
name: name
location: location
tags: tags
sku: {
name: sku
}
kind: kind
properties: {
publicNetworkAccess: publicNetworkAccess

}
}

@description('Resource Name')
output name string = cognitiveService.name

@description('Resource Id')
output id string = cognitiveService.id

@description('Endpoint')
output endpoint string = cognitiveService.properties.endpoint
68 changes: 52 additions & 16 deletions infra/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,36 @@ param name string
@description('Primary location for all resources')
param location string

@secure()
@description('PostGreSQL Server administrator password')
param postgresServerPassword string

@description('Id of the user or app to assign application roles')
param principalId string = ''

@secure()
@description('PostGreSQL Server administrator password')
param postgresAdminPassword string

var resourceToken = toLower(uniqueString(subscription().id, name, location))
var tags = { 'azd-env-name': name }
var prefix = '${name}-${resourceToken}'

var postgresServerName = '${prefix}-postgres'
var postgresServerAdmin = 'flaskadmin'
var postgresDatabaseName = 'transtel'
var rgName = '${prefix}-rg'

resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: '${prefix}-rg'
name: rgName
location: location
tags: tags
}

module cognitiveService 'core/ai/cognitiveservices.bicep' = {
name: 'cognitiveservice'
scope: resourceGroup
params: {
name: '${prefix}-cognitiveservice'
location: location
sku: 'S1'
kind: 'TextTranslation'
publicNetworkAccess: 'Enabled'
}
}

// Store secrets in a keyvault
module keyVault './core/security/keyvault.bicep' = {
john0isaac marked this conversation as resolved.
Show resolved Hide resolved
name: 'keyvault'
Expand All @@ -42,16 +51,40 @@ module keyVault './core/security/keyvault.bicep' = {
}
}

module keyVaultSecret './core/security/keyvault-secret.bicep' = {
name: 'keyvault-secret'
module postgreSQLDBSecret './core/security/keyvault-secret.bicep' = {
name: 'keyvaultsecret-postgresql'
scope: resourceGroup
params: {
keyVaultName: keyVault.outputs.name
name: 'postgresServerPassword'
secretValue: postgresServerPassword
name: 'postgresAdminPassword'
secretValue: postgresAdminPassword
}
}

module cognitiveServiceSecret './app/security.bicep' = {
name: 'keyvaultsecret-cognitiveservice'
scope: resourceGroup
params: {
rgName: rgName
keyVaultName: keyVault.outputs.name
name: 'cognitiveServiceKey'
cognitiveServiceName: cognitiveService.outputs.name
}
}

module webaccess './core/security/keyvault-access.bicep' = {
name: 'web-keyvault-access'
scope: resourceGroup
params: {
keyVaultName: keyVault.outputs.name
principalId: web.outputs.identityPrincipalId
}
}

var postgresServerName = '${prefix}-postgres'
var postgresServerAdmin = 'admin${uniqueString(resourceGroup.id)}'
var postgresDatabaseName = 'transtel'


module postgresServer 'core/database/postgresql/flexibleserver.bicep' = {
name: 'postgresql'
Expand All @@ -68,8 +101,8 @@ module postgresServer 'core/database/postgresql/flexibleserver.bicep' = {
storageSizeGB: 32
}
version: '13'
administratorLogin: 'flaskadmin'
administratorLoginPassword: postgresServerPassword
administratorLogin: postgresServerAdmin
administratorLoginPassword: postgresAdminPassword
databaseNames: [postgresDatabaseName]
allowAzureIPsFirewall: true
}
Expand All @@ -91,9 +124,11 @@ module web 'core/host/appservice.bicep' = {
DBHOST: '${postgresServerName}.postgres.database.azure.com'
DBNAME: postgresDatabaseName
DBUSER: postgresServerAdmin
DBPASS: postgresServerPassword
DBPASS: '@Microsoft.KeyVault(VaultName=${keyVault.outputs.name};SecretName=postgresAdminPassword)'
FLASK_APP: 'src'
AZURE_TRANSLATE_API_KEY: '@Microsoft.KeyVault(VaultName=${keyVault.outputs.name};SecretName=cognitiveServiceKey)'
}
keyVaultName: keyVault.outputs.name
}
}

Expand All @@ -114,3 +149,4 @@ module appServicePlan 'core/host/appserviceplan.bicep' = {

output WEB_URI string = web.outputs.uri
output AZURE_LOCATION string = location
output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name
4 changes: 2 additions & 2 deletions infra/main.parameters.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"principalId": {
"value": "${AZURE_PRINCIPAL_ID}"
},
"postgresServerPassword": {
"value": "$(secretOrRandomPassword ${AZURE_KEY_VAULT_NAME} postgresServerPassword)"
"postgresAdminPassword": {
"value": "$(secretOrRandomPassword ${AZURE_KEY_VAULT_NAME} postgresAdminPassword)"
}
}
}
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ Flask
requests
psycopg2
Flask-SQLAlchemy
Flask-Migrate
Flask-Migrate
6 changes: 4 additions & 2 deletions src/translations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

def translate_with_azure(text, from_lang, to_lang):
url = "https://api.cognitive.microsofttranslator.com/translate"
location = "westus2"
# REGION_NAME is read-only env var from App Service Environment
# Reference: https://learn.microsoft.com/azure/app-service/reference-app-settings#app-environment
location = os.environ.get("REGION_NAME", "NoRegionFound")
params = {"api-version": "3.0", "from": from_lang, "to": to_lang}
headers = {
"Ocp-Apim-Subscription-Key": os.environ.get("AZURE_TRANSLATE_API_KEY", "NoKeyFound"),
Expand All @@ -18,6 +20,6 @@ def translate_with_azure(text, from_lang, to_lang):

request = requests.post(url, params=params, headers=headers, json=body)
response = request.json()
if (type(response) is dict) and (err := response.get("error", None)):
if isinstance(response, dict) and (err := response.get("error", None)):
return None, err["message"], "AZURE"
return response[0]["translations"][0]["text"], None, "AZURE"