diff --git a/infrastructure/README.md b/infrastructure/README.md new file mode 100644 index 0000000..dbebb44 --- /dev/null +++ b/infrastructure/README.md @@ -0,0 +1,43 @@ +# Infrastructure + +##### The [bicep-template](./azureInfrastructure.bicep) contains the infrastructure to run a production and development environment for this application in [Microsoft Azure](https://azure.microsoft.com/). + +For deploying the app you have to follow these steps: + +1. Download the Azure CLI (guide [here](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)) +2. Log in to your preferred account using `az login` +3. Create a Resource Group for the application by the command (Change the name and the location if another is preferred): + `az group create --name vote --location "Norway East"` +4. Deploy the bicep template into the newly created Resource group by entering this command from this folder (replace name of resource group if another is used): +5. `az deployment group create --resource-group test --template-file ./infrastructure/azureInfrastructure.bicep` + +Now you have all the resources set up, but there are still some manual steps: + +1. Set up a custom domain + + The application has to be hosted on the same domain/subdomain for cookies and CORS to behave correctly. + For example, the frontend can be assigned to vote.ntnui.no, then the backend has to run on a subdomain of the frontend. For example, api.vote.ntnui.no. This has to be configured manually in Azure. + (In the future this could probably be automated as well?) + +2. Allow only the backend IP to access the database. + + Because the backend is dependent on the database to exist to obtain the database connection string, the database is created before the backend. Therefore the bicep script won't know the backend IP because it does not exist yet. By default, it there allows all IPs, which is not ideal security-wise. + (You obviously still need the URI, username, and password) + +### Deploying code from GitHub + +##### Now that the infrastructure is up and running it's time to add some content โœจ + +This repository contains some [workflows](../.github/workflows/). The ones containing "deploy" deploys either the backend or the frontend to the development or the production environment. + +In the backend workflows, you have to add the correct `app-name` and `slot-name`. +The backend workflows also contain an `publish-profile`. The publish profiles are stored in the secrets section of this GitHub repository. These secrets have to be updated with the new ones for the workflows to function. + +![Image](azure-publish-profile.png) +The secret can be downloaded from here or by using Azure CLI. + +It is almost the same for the frontend workflows, except here the workflow contains a `azure_static_web_apps_api_token`. You find this token almost the same way as the publish profile, but it is named `deployment token` in the Static Web App settings. + +##### When all the above steps are completed, your next commit will automagically deploy to Azure, and the service are ready for production! ๐Ÿš€ + +(If one day you do not need the service anymore, you only need to delete the resource group, and you are back to where you started ๐Ÿงน) diff --git a/infrastructure/azure-publish-profile.png b/infrastructure/azure-publish-profile.png new file mode 100644 index 0000000..d68a5d8 Binary files /dev/null and b/infrastructure/azure-publish-profile.png differ diff --git a/infrastructure/azureInfrastructure.bicep b/infrastructure/azureInfrastructure.bicep new file mode 100644 index 0000000..45ed6aa --- /dev/null +++ b/infrastructure/azureInfrastructure.bicep @@ -0,0 +1,180 @@ +param appName string = 'vote' +param location string = 'Norway East' +param staticWebAppLocation string = 'westeurope' + +/* + Using a S1 plan to support development slots. + Also using a S1 plan to support more than 350 concurrent web socket connections. + If downgrading to a cheaper plan, such as B1, you could run into issues with the number of concurrent web socket connections under peak load. + Also you have to remove the dev slot, and give it a separate Web App. + */ +resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = { + name: '${appName}-plan' + location: location + sku: { + name: 'S1' + tier: 'Standard' + } + properties: { + reserved: true + } +} + +resource voteDB 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { + name: '${appName}-db' + location: location + tags: { + defaultExperience: 'Azure Cosmos DB for MongoDB API' + 'hidden-cosmos-mmspecial': '' + } + kind: 'MongoDB' + identity: { + type: 'None' + } + properties: { + enableFreeTier: false + databaseAccountOfferType: 'Standard' + apiProperties: { + serverVersion: '4.2' + } + locations: [ + { + locationName: location + failoverPriority: 0 + isZoneRedundant: false + } + ] + capabilities: [ + { + name: 'EnableMongo' + } + { + name: 'DisableRateLimitingResponses' + } + { + name: 'EnableServerless' + } + ] + backupPolicy: { + type: 'Continuous' + continuousModeProperties: { + tier: 'Continuous7Days' + } + } + } +} + +resource databaseAccounts_production 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2023-04-15' = { + parent: voteDB + name: 'production' + properties: { + resource: { + id: 'production' + } + } +} + +resource databaseAccounts_development 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2023-04-15' = { + parent: voteDB + name: 'development' + properties: { + resource: { + id: 'development' + } + } +} + +resource backend 'Microsoft.Web/sites@2022-09-01' = { + name: '${appName}-backend' + location: location + kind: 'app,linux' + properties: { + serverFarmId: appServicePlan.id + httpsOnly: true + siteConfig: { + numberOfWorkers: 1 + linuxFxVersion: 'NODE|18-lts' + appSettings: [ + { + name: 'DB_URI' + value: voteDB.listConnectionStrings().connectionStrings[0].connectionString + } + { + name: 'BACKEND_PORT' + value: '8080' + } + { + name: 'NODE_ENV' + value: 'production' + } + { + name: 'NTNUI_TOOLS_API_URL' + value: 'https://api.ntnui.no/' + } + ] + } + } +} + +resource backendDevSlot 'Microsoft.Web/sites/slots@2022-09-01' = { + parent: backend + location: location + name: 'dev' + properties: { + httpsOnly: true + siteConfig: { + numberOfWorkers: 1 + linuxFxVersion: 'NODE|18-lts' + appSettings: [ + { + name: 'DB_URI' + value: voteDB.listConnectionStrings().connectionStrings[0].connectionString + } + { + name: 'BACKEND_PORT' + value: '8080' + } + { + name: 'NODE_ENV' + value: 'development' + } + { + name: 'NTNUI_TOOLS_API_URL' + value: 'https://dev.api.ntnui.no/' + } + ] + } + } +} + +resource frontend 'Microsoft.Web/staticSites@2022-09-01' = { + name: '${appName}-frontend' + location: staticWebAppLocation + properties: { + repositoryUrl: 'https://github.com/NTNUI/vote2/' + branch: 'main' + buildProperties: { + appLocation: '/frontend' + } + } + sku: { + tier: 'Free' + name: 'Free' + } +} + +resource frontendDev 'Microsoft.Web/staticSites@2022-09-01' = { + name: '${appName}-frontend-dev' + location: staticWebAppLocation + properties: { + repositoryUrl: 'https://github.com/NTNUI/vote2/' + branch: 'dev' + buildProperties: { + appLocation: '/frontend' + } + } + sku: { + tier: 'Free' + name: 'Free' + } +}