Discord
'Why Discord?' you might ask. Well why not? Currently my colleague Soufian with whom I worked on this is busy with getting a discord bot up and running in combination with Microsoft Teams. In this I have mainly focused on getting the right resources (services) together as well as build up the YAML for the largest part to deploy the bot itself.
In this blog I'll explain the deployment and services. In another blog, Soufian will go more in-depth about the developer's side of things.
Azure Web Services
When looking at a service to run a Discord bot in, it's always quite tedious to find a proper service. An Azure Function would be great, but since it's serverless, it is not always online, which we do want. Now Azure Functions can also be Durable, meaning they are continuous but not cheap.
After that we defaulted to an Azure App Service (Web App), but this still didn't work well since the wwwwroot expects a website and not a continuous running script. But luckily a Windows-based App Service has the option for a WebJob, which allows specifically for continuous scripts to run! With this we had a solution to work with.
NOTE: B1 series or higher App Service is required to run continuous WebJobs.
For now, we created the App Service via the portal. We will later change the deployment to a bicep.
Azure Pipeline
To deploy to the App Service in Azure a YAML is needed for the Azure Pipeline to run. It was quite a hassle to get this up and running in a proper way, since typescript had some issues and finally it needed to get packed as a .ZIP file, since the Windows WebJob requires a .ZIP file to run.
Let's look at the steps, piece by piece.
Node and Npm
First up, get the right node version. Node 16 is still in preview for the Windows based App Services in Azure, while it has been out for a while for developers and so a lot of packages (including Discord) require it now. So in that case we make sure that the Agent within YAML also runs on Node 16.
A simple npm run build
works perfectly to get the code to build.
- task: NodeTool@0
inputs:
versionSpec: '16.x'
displayName: 'Install node.js'
- script: echo "running build..." & npm run build
displayName: 'Installing dependencies'
Copy Files
As stated, we had some issues getting typescript to work, and Soufian figured out we needed to exclude the remaining typescript files from being packaged. To do this, Soufian came with the idea to copy the files to another location while excluding the typescript files after building.
- task: CopyFiles@2
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**
!*.ts
!Commands/*.ts
!Commands/**/*.ts
!Events/*.ts
!Handlers/*.ts
!Utilities/*.ts
TargetFolder: '$(Build.ArtifactStagingDirectory)'
OverWrite: true
Zipping and Publishing the Artifact
As stated the WebJob on the Windows App Service needs a .ZIP file, which is not quite as common, so this could be done by a ArchiveFiles task.
Publishing an Artifact is quite common so this might not need much explanation, only that we need it to get published so it could be used in the next stage (Deploy).
- task: ArchiveFiles@1
inputs:
rootFolder: '$(Build.ArtifactStagingDirectory)'
includeRootFolder: false
archiveType: 'default'
tarCompression: none
archiveFile: '$(Build.ArtifactStagingDirectory)\$(Build.BuildId).zip'
replaceExistingArchive: true
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)\$(Build.BuildId).zip'
ArtifactName: 'drop'
publishLocation: 'Container'
Download Artifact and Deploy via Azure CLI
Since our Deploy is a different stage, as is the better practice, we needed to redownload the artifact in order to be able to use it. Luckily this is a step that only takes a few seconds and so the real process can run, which is the Azure CLI task. Why Azure CLI and not the App Service task? Well, the App Service task is quite limited in its options and since we wanted more control due to a different target path, it shouldn't go in the wwwroot
folder as stated before.
- task: DownloadBuildArtifacts@1
inputs:
buildType: 'current'
downloadType: 'single'
artifactName: 'drop'
downloadPath: '$(System.ArtifactsDirectory)'
- task: AzureCLI@2
inputs:
azureSubscription: $(AzureServiceConnection)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: "az webapp deploy --resource-group $(ResourceGroup) --name $(AppName) --src-path '$(System.ArtifactsDirectory)\\drop/$(Build.BuildId).zip' --type zip --clean true --target-path App_Data/jobs/continuous/discordWebJob"
The complete YAML
Below you will find the complete YAML you can reuse for your deployments. Have a look a Soufian's blog about the code and what needs to happen with that!
trigger:
- master
variables:
AzureServiceConnection: {Your Azure service connection}
ResourceGroup: {Your resource group name}
AppName: {Your App service name}
pool:
vmImage: windows-latest
stages:
- stage: build
displayName: Build
jobs:
- job: build
steps:
- task: NodeTool@0
inputs:
versionSpec: '16.x'
displayName: 'Install node.js'
- script: echo "running build..." & npm run build
displayName: 'Installing dependencies'
- task: CopyFiles@2
inputs:
SourceFolder: '$(Build.SourcesDirectory)'
Contents: |
**
!*.ts
!Commands/*.ts
!Commands/**/*.ts
!Events/*.ts
!Handlers/*.ts
!Utilities/*.ts
TargetFolder: '$(Build.ArtifactStagingDirectory)'
OverWrite: true
- task: ArchiveFiles@1
inputs:
rootFolder: '$(Build.ArtifactStagingDirectory)'
includeRootFolder: false
archiveType: 'default'
tarCompression: none
archiveFile: '$(Build.ArtifactStagingDirectory)\$(Build.BuildId).zip'
replaceExistingArchive: true
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)\$(Build.BuildId).zip'
ArtifactName: 'drop'
publishLocation: 'Container'
- stage: deploy
displayName: Deploy
jobs:
- job: deploying
steps:
- task: DownloadBuildArtifacts@1
inputs:
buildType: 'current'
downloadType: 'single'
artifactName: 'drop'
downloadPath: '$(System.ArtifactsDirectory)'
- task: AzureCLI@2
inputs:
azureSubscription: $(AzureServiceConnection)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: "az webapp deploy --resource-group $(ResourceGroup) --name $(AppName) --src-path '$(System.ArtifactsDirectory)\\drop/$(Build.BuildId).zip' --type zip --clean true --target-path App_Data/jobs/continuous/discordWebJob"
If you have the code for the Discord bot in your repo and use the above YAML, you should be go to run the pipeline.
What's next?
I don't know yet, stay tuned for next week and let's see!