Browse Source

Merge pull request #72 from CatalystCode/feature/azure

Feature/Add Azure deployment automation
pull/77/head
Hiroki Nakayama 6 years ago
committed by GitHub
parent
commit
4a246d8de5
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 321 additions and 0 deletions
  1. 9
      README.md
  2. 27
      app/server/management/commands/create_admin.py
  3. 35
      app/server/management/commands/wait_for_db.py
  4. 240
      azuredeploy.json
  5. 9
      tools/create-admin.sh
  6. 1
      tools/run.sh

9
README.md

@ -26,6 +26,15 @@ Final demo is one of the sequence to sequence tasks, machine translation. Since
![Machine Translation](./docs/translation.gif) ![Machine Translation](./docs/translation.gif)
## Deployment
### Azure
Doccano can be deployed to Azure ([Web App for Containers](https://azure.microsoft.com/en-us/services/app-service/containers/) +
[PostgreSQL database](https://azure.microsoft.com/en-us/services/postgresql/)) by clicking on the button below:
[![Deploy to Azure](https://azuredeploy.net/deploybutton.svg)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fchakki-works%2Fdoccano%2Fmaster%2Fazuredeploy.json)
## Features ## Features
* Collaborative annotation * Collaborative annotation

27
app/server/management/commands/create_admin.py

@ -0,0 +1,27 @@
from django.contrib.auth.management.commands import createsuperuser
from django.core.management import CommandError
class Command(createsuperuser.Command):
help = 'Non-interactively create an admin user'
def add_arguments(self, parser):
super(Command, self).add_arguments(parser)
parser.add_argument('--password', default=None,
help='The password for the admin.')
def handle(self, *args, **options):
password = options.get('password')
username = options.get('username')
if password and not username:
raise CommandError('--username is required if specifying --password')
super(Command, self).handle(*args, **options)
if password:
database = options.get('database')
db = self.UserModel._default_manager.db_manager(database)
user = db.get(username=username)
user.set_password(password)
user.save()

35
app/server/management/commands/wait_for_db.py

@ -0,0 +1,35 @@
import sys
import time
from django.db import connection
from django.db.utils import OperationalError
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Blocks until the database is available'
def add_arguments(self, parser):
parser.add_argument('--poll_seconds', type=float, default=3)
parser.add_argument('--max_retries', type=int, default=60)
def handle(self, *args, **options):
max_retries = options['max_retries']
poll_seconds = options['poll_seconds']
for retry in range(max_retries):
try:
connection.ensure_connection()
except OperationalError as ex:
self.stdout.write(
'Database unavailable on attempt {attempt}/{max_retries}:'
' {error}'.format(
attempt=retry + 1,
max_retries=max_retries,
error=ex))
time.sleep(poll_seconds)
else:
break
else:
self.stdout.write(self.style.ERROR('Database unavailable'))
sys.exit(1)

240
azuredeploy.json

@ -0,0 +1,240 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appName":{
"type": "string",
"minLength": 1,
"metadata": {
"description": "The name for the webapp. Must be globally unique."
}
},
"secretKey":{
"type": "securestring",
"minLength": 16,
"metadata": {
"description": "The value to use as the Django secret key."
}
},
"adminUserName":{
"type": "string",
"minLength": 1,
"metadata": {
"description": "The user name for the admin account."
}
},
"adminContactEmail":{
"type": "string",
"minLength": 1,
"metadata": {
"description": "The contact email address for the admin account."
}
},
"adminPassword":{
"type": "securestring",
"minLength": 16,
"metadata": {
"description": "The password for the admin account."
}
},
"appServiceSku": {
"type": "string",
"defaultValue": "B2",
"allowedValues": [
"B1",
"B2",
"B3",
"S1",
"S2",
"S3"
],
"metadata": {
"description": "The SKU of the webapp hosting tier."
}
},
"databaseCores": {
"type": "int",
"defaultValue": 2,
"allowedValues": [
2,
4,
8,
16,
32,
64
],
"metadata": {
"description": "The number of vCores to provision for the PostgreSQL server."
}
},
"databaseSize": {
"type": "int",
"minValue": 51200,
"defaultValue": 51200,
"metadata": {
"description": "The storage capacity to provision for the PostgreSQL server."
}
},
"databaseName": {
"type": "string",
"minLength": 1,
"defaultValue": "doccano",
"metadata": {
"description": "The name of the database to provision on the PostgreSQL server."
}
},
"dockerImageName": {
"type": "string",
"minLength": 1,
"defaultValue": "cwolff/doccano:latest",
"metadata": {
"description": "The Docker image to deploy."
}
}
},
"variables": {
"location": "[resourceGroup().location]",
"databaseSkuTier": "GeneralPurpose",
"databaseSkuFamily": "Gen5",
"databaseSkuName": "[concat('GP_', variables('databaseSkuFamily'), '_', parameters('databaseCores'))]",
"databaseConnectionString": "[concat('pgsql://', parameters('adminUserName'), '@', variables('databaseServerName'), ':', parameters('adminPassword'), '@', variables('databaseServerName'), '.postgres.database.azure.com:5432/', parameters('databaseName'))]",
"databaseVersion": "9.6",
"databaseServerName": "[concat(parameters('appName'),'-state')]",
"setupScriptName": "[concat(parameters('appName'),'-setup')]",
"appServicePlanName": "[concat(parameters('appName'),'-hosting')]",
"env": [
{
"name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
"value": "false"
},
{
"name": "DEBUG",
"value": "False"
},
{
"name": "SECRET_KEY",
"value": "[parameters('secretKey')]"
},
{
"name": "DATABASE_URL",
"value": "[variables('databaseConnectionString')]"
}
]
},
"resources": [
{
"apiVersion": "2017-08-01",
"type": "Microsoft.Web/serverfarms",
"kind": "linux",
"name": "[variables('appServicePlanName')]",
"location": "[variables('location')]",
"properties": {
"reserved": true
},
"dependsOn": [],
"sku": {
"name": "[parameters('appServiceSku')]"
}
},
{
"apiVersion": "2017-12-01",
"type": "Microsoft.DBforPostgreSQL/servers",
"location": "[variables('location')]",
"name": "[variables('databaseServerName')]",
"sku": {
"name": "[variables('databaseSkuName')]",
"tier": "[variables('databaseSkuTier')]",
"family": "[variables('databaseSkuFamily')]",
"capacity": "[parameters('databaseCores')]",
"size": "[parameters('databaseSize')]"
},
"properties": {
"version": "[variables('databaseVersion')]",
"administratorLogin": "[parameters('adminUserName')]",
"administratorLoginPassword": "[parameters('adminPassword')]",
"storageMB": "[parameters('databaseSize')]"
},
"resources": [
{
"name": "allowAzure",
"type": "firewallrules",
"apiVersion": "2017-12-01",
"location": "[variables('location')]",
"properties": {
"startIpAddress": "0.0.0.0",
"endIpAddress": "0.0.0.0"
},
"dependsOn": [
"[resourceId('Microsoft.DBforPostgreSQL/servers/', variables('databaseServerName'))]"
]
},
{
"name": "[parameters('databaseName')]",
"type": "databases",
"apiVersion": "2017-12-01",
"properties": {
"charset": "utf8",
"collation": "English_United States.1252"
},
"dependsOn": [
"[resourceId('Microsoft.DBforPostgreSQL/servers/', variables('databaseServerName'))]"
]
}
]
},
{
"name": "[variables('setupScriptName')]",
"type": "Microsoft.ContainerInstance/containerGroups",
"apiVersion": "2018-10-01",
"location": "[variables('location')]",
"properties": {
"containers": [
{
"name": "createadmin",
"properties": {
"image": "[parameters('dockerImageName')]",
"command": [
"tools/create-admin.sh",
"[parameters('adminUserName')]",
"[parameters('adminContactEmail')]",
"[parameters('adminPassword')]"
],
"environmentVariables": "[variables('env')]",
"resources": {
"requests": {
"cpu": "1",
"memoryInGb": "1.5"
}
}
}
}
],
"osType": "Linux",
"restartPolicy": "Never"
},
"dependsOn": [
"[resourceId('Microsoft.DBforPostgreSQL/servers/', variables('databaseServerName'))]"
]
},
{
"type": "Microsoft.Web/sites",
"apiVersion": "2016-08-01",
"name": "[parameters('appName')]",
"kind": "app,linux,container",
"location": "[variables('location')]",
"dependsOn": [
"[resourceId('Microsoft.DBforPostgreSQL/servers/', variables('databaseServerName'))]",
"[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
],
"properties": {
"name": "[parameters('appName')]",
"siteConfig": {
"linuxFxVersion": "[concat('DOCKER|', parameters('dockerImageName'))]",
"alwaysOn": true,
"appSettings": "[variables('env')]"
},
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
}
}
]
}

9
tools/create-admin.sh

@ -0,0 +1,9 @@
#!/usr/bin/env bash
if [[ "$#" -ne 3 ]]; then echo "Usage: $0 <username> <email> <password>" >&2; exit 1; fi
set -o errexit
python app/manage.py wait_for_db
python app/manage.py migrate
python app/manage.py create_admin --noinput --username="$1" --email="$2" --password="$3"

1
tools/run.sh

@ -4,5 +4,6 @@ set -o errexit
if [[ ! -d "app/staticfiles" ]]; then python app/manage.py collectstatic --noinput; fi if [[ ! -d "app/staticfiles" ]]; then python app/manage.py collectstatic --noinput; fi
python app/manage.py wait_for_db
python app/manage.py migrate python app/manage.py migrate
gunicorn --bind="${BIND:-127.0.0.1:8000}" --workers="${WORKERS:-1}" --pythonpath=app app.wsgi gunicorn --bind="${BIND:-127.0.0.1:8000}" --workers="${WORKERS:-1}" --pythonpath=app app.wsgi
Loading…
Cancel
Save