My colleague David asked about a very interesting feature.
"What if we can provision Azure Static Web App and deploy the app at the same time? If we can, we just simply hand over our PoC repository to developers so that they just run it directly from there."
Although two challenges exist to achieve this idea, if we can combine both challenges, devs can autopilot everything from the resource creating to the app deployment, which will result in improving the development experiences. Throughout this post, I'm going to discuss how to offer the autopilot feature for your application repository.
You can download the sample code from this GitHub repository below.
This provides sample GitHub Actions workflows and Bicep files for devs to autopilot Azure Static Web Apps from resource provisioning to app deployment in one-click.
ASWA AutoPilot Sample
This provides sample GitHub Actions workflows and Bicep files for devs to autopilot Azure Static Web Apps from resource provisioning to app deployment in just one mouse click.
Getting Started
Auto-Pilot via Azure Portal
Make sure that the autopilot through Azure Portal only provision resources.
Then, run the following PowerShell script to deploy the app:
./infra/Deploy-App.ps1
NOTE: You need GitHub CLI to run the PowerShell script.
OH WAIT! IT'S TWO STEPS!
Don't worry. Here's another one for you.
Auto-Pilot via GitHub Actions
To run this autopilot through GitHub Actions, you need to add the following two secrets to your repository:
Both front-end and back-end apps can be combined with Azure Static Web Apps (ASWA), and both ASWA and Cosmos DB are provisioned through GitHub Actions. So the overall architecture might look like this:
Azure Resource Provisioning
Let's build Azure resources based on the architecture diagram above. Azure Bicep would be the choice for resource provisioning.
Cosmos DB
First of all, declare Cosmos DB instance. We're going to use the Serverless tier and the SQL API, give the database name of AdventureWorks and the container name of products. Here's the sample Bicep file. For brevity, irrelevant details are omitted.
When you see the output value at the bottom line, it uses the listKeys() function that returns the connection string of the Cosmos DB instance. This value will be used for ASWA integration.
Azure Static Web Apps
Let's declare ASWA instance. This time, we use the basic free tier and add the Cosmos DB connection string for the back-end API to use. Without unnecessary details, the Bicep file might look like this:
// staticWebApp.bicep: Azure Static Web AppsparamresourceNamestringparamresourceLocationstring@secure()paramcosmosDbConnectionStringstringresourcesttapp'Microsoft.Web/staticSites@2021-03-01'={name:'sttapp-${resourceName}'location:resourceLocationsku:{name:'Free'}...}resourcesttappconfig'Microsoft.Web/staticSites/config@2021-03-01'={name:'${sttapp.name}/appsettings'properties:{ConnectionStrings_CosmosDB:cosmosDbConnectionString}}// OutputsoutputdeploymentKeystring=sttapp.listSecrets().properties.apiKey
The output value at the bottom line uses the listSecreets() function to return the deployment key for the CI/CD pipeline to use. This value will be used while deploying the app within the GitHub Actions workflow.
main.bicep – Orchestration
We've got both Cosmos DB and ASWA instances declared by Bicep. Now, we need the orchestration file to create both resources. Here's the script. It uses the module keyword to call the pre-defined resources.
// main.bicep: OrchestrationparamresourceNamestringparamcosmosDbLocationstringparamcosmosDbDatabaseNamestringparamcosmosDbContainerNamestringparamstaticAppLocationstring// Cosmos DBmodulecosdba'./cosmosDb.bicep'={name:'CosmosDB'params:{resourceName:resourceNamelocation:cosmosDbLocationdatabaseName:cosmosDbDatabaseNamecontainerName:cosmosDbContainerName}}// Static Web Appmodulesttapp'./staticWebApp.bicep'={name:'StaticWebApp'params:{resourceName:resourceNamelocation:staticAppLocationcosmosDbConnectionString:cosdba.outputs.connectionString}}// OutputsoutputconnectionStringstring=cosdba.outputs.connectionStringoutputdeploymentKeystring=sttapp.outputs.deploymentKey
The output values return the Cosmos DB connection string and ASWA deployment key.
azuredeploy.bicep – Subscription Level Scope
Although we can use the main.bicep for resource creation, we also need to provision the resource group for the "autopilot" feature. Therefore, let's create the azuredeploy.bicep that creates the resource group and provisions resources into the resource group. In this file, the first line should declare the targetScope value as subscription.
Because this Bicep file offers the autopilot feature, it minimises user interventions by providing possible options. The following Bicep file shows how to give options to developers to choose, except resourceName.
// azuredeploy.biceptargetScope='subscription'paramnamestring@allowed([...'Central US''East Asia''East US 2''Korea Central''West Europe''West US 2'...])paramcosmosDbLocationstring='Korea Central'paramcosmosDbDatabaseNamestring='AdventureWorks'paramcosmosDbContainerNamestring='products'@allowed(['Central US''East Asia''East US 2''West Europe''West US 2'])paramstaticAppLocationstring='East Asia'// Resource Groupresourcerg'Microsoft.Resources/resourceGroups@2021-04-01'={name:'rg-${name}'location:cosmosDbLocation}// Resource Orchestrationmoduleresources'./main.bicep'={name:'Resources'scope:rgparams:{resourceName:namecosmosDbLocation:rg.locationcosmosDbDatabaseName:cosmosDbDatabaseNamecosmosDbContainerName:cosmosDbContainerNamestaticAppLocation:staticAppLocation}}// OutputsoutputconnectionStringstring=resources.outputs.connectionStringoutputdeploymentKeystring=resources.outputs.deploymentKey
The output values represent the Cosmos DB connection string and the ASWA deployment key.
We're almost there! Run the following command to convert azuredeploy.bicep to azuredeploy.json ARM template for Azure Portal to understand.
azbicepbuild-fazuredeploy.bicep
NOTE: Each Bicep file above represents individual resource, orchestration and subscription level scoping, respectively, based on my preference - single responsibility principle. But you can build up one massive azuredeploy.bicep if you like.
Once the ARM template, azuredeploy.json, is generated, connect its GitHub URL to the "Deploy to Azure" button.
You will see the Azure Portal screen that provision resources when you click the button above. All the rest fields have already been filled with default values, except the Name field.
Once provisioning completes, you will be able to see the resources on Azure Portal:
ASWA contains the Cosmos DB connection string as well.
So far, we've completed the resource provisioning. However, we still need the app deployment. Let's move on.
Azure App Deployment
To deploy ASWA to Azure, CI/CD pipeline is the only way at the time of this writing. Therefore, it's critical to rely on GitHub Actions workflow. What if we want to use the CI/CD pipeline from the other vendors? We should decouple this GitHub Actions dependency first.
Fortunately, our provisioned ASWA instance hasn't got connected to any CI/CD pipeline yet, but it has the deployment key for other CI/CD pipelines to use. With this key, we can connect our own GitHub Actions workflow. So let's use this deployment key to connect the GitHub Actions workflow controlled by us.
Deployment Key as GitHub Secret
Let's store the deployment key to GitHub secret, AZURE_STATIC_WEB_APPS_API_TOKEN.
GitHub Actions Workflow – workflow_call
There are roughly two events for the app deployment.
As we need to deploy the app in both cases, workflow_call is the best bet to be called from both events. The following workflow shows how to write the workflow. The caller workflow passes parameters and secrets and executes the Azure/static-web-apps-deploy action. Because all the parameter values have their defaults, the caller workflow only needs to pass the secrets.
# app-deploy.yamlname:'AppDeploy'on:workflow_call:inputs:job_name:type:stringrequired:falsedefault:'DeployStaticWebApp'app_location:type:stringrequired:falsedefault:appapi_location:type:stringrequired:falsedefault:apioutput_location:type:stringrequired:falsedefault:wwwrootsecrets:gh_token:required:trueaswa_token:required:truejobs:deploy:name:${{ inputs.job_name }}runs-on:ubuntu-lateststeps:-name:Checkout the repouses:actions/checkout@v2with:submodules:true-name:Deploy Static Web Appuses:Azure/static-web-apps-deploy@v1with:azure_static_web_apps_api_token:${{ secrets.aswa_token }}repo_token:${{ secrets.gh_token }}action:uploadapp_location:${{ inputs.app_location }}api_location:${{ inputs.api_location }}output_location:${{ inputs.output_location }}
GitHub Actions Workflow – workflow_dispatch
Through this workflow_dispatch workflow, we can call the workflow_call workflow, app-deploy.yaml, outside GitHub or other workflows. Here's the sample workflow. All parameters are the same as the ones in app-deploy.yaml, except their namings.
NOTE: To run the GitHub CLI command above, you should be logged in beforehand. If you're unsure, run the gh auth status command to make sure whether you're already logged in or not. Then, run the gh auth login command to log in if you're logged out.
You're now able to deploy the app after the resource provisioning.
GitHub Actions Workflow – push/pull_request
You've deployed the app for the first time through the autopilot feature. Now, you've got the code changes. Then, you also need another workflow reacting both push and pull_request events. As you've already got the workflow_call workflow, app-deploy.yaml, you only need a wrapper workflow for it. Here's the one:
This workflow is triggered by either push or pull_request. With this workflow, when you change your code, it will be deployed through this main-app.yaml workflow.
Putting Altogether
By the way, I'm still not happy about it because it's the TWO-STEP process. Can we merge two operations in one? The most realistic scenario at this time of writing is that we make use of the workflow_dispatch event that provisions the resources and deploys the app. Let's move on.
GitHub Actions Workflow – Resource Provisioning
We've already got all the necessary Bicep files. Therefore, write another workflow_call workflow to run the Bicep file. Here's the sample workflow. Except for the resource_name parameter, all parameters have default values.
To provision resources to Azure, Azure CLI is required. The PowerShell script, Provision-Resources.ps1, has all the necessary details. This PowerShell script returns the provisioning details, including the ASWA deployment key. The deployment key is stored in GitHub secret at the final action.
GitHub Actions Workflow – Autopilot
Here's the last GitHub Actions workflow using the workflow_dispatch event. The input parameters provide as many options as possible to minimise user inputs, except for the resourceName parameter.
When you see the workflow above, the first job calls another workflow_call event, resource-provision.yaml, while the second job calls the workflow_dispatch event. So why does it call the workflow_dispatch event, main-dispatch.yaml instead of calling workflow_call event, app-deploy.yaml?
Unless you're using the deployment key stored in the first workflow_call job, resource-provision.yaml, the second job can use another workflow_call, app-deploy.yaml. However, any GitHub repository secret updates can't be recognised within the same pipeline context. Therefore, we need to create a new pipeline context by calling the workflow_dispatch event main-dispatch.yaml to get the newly stored ASWA deployment key.
We got the whole autopilot landscape. But we need two more steps – adding two secrets, AZURE_CREDENTIALS and PA_TOKEN.
It's safe to remove the existing secret, AZURE_STATIC_WEB_APPS_API_TOKEN. The secret value will be automatically updated during the autopilot execution if you don't.
AZURE_CREDENTIALS
The workflow uses Azure CLI, which requires the login details. Run the following command to create the Azure login credentials and store it to AZURE_CREDENTIALS.
If you want to know more details, refer to this document.
PA_TOKEN
The GitHub personal access token (PAT) is required to store the ASWA deployment key within the workflow_dispatch workflow and call the other workflow_dispatch event. Use this document to generate a PAT and store it to PA_TOKEN.
Autopilot Execution
Once all of the above is done, let's run the autopilot feature.
Go to the "Actions" tab.
Click the "Autopilot" tab.
Click the "Run workflow" button.
Enter the resource name.
Choose the Cosmos DB location.
Choose the Azure Static Web App location.
Click the "Run workflow" button.
Once the autopilot is done, both resource provisioning and app deployment are completed at once. As you can see in the picture below, it's expected to see the "No product found" message because of no record stored in the Cosmos DB instance.
The autopilot feature is working as expected.
Known Issues for Improvements
Although we've implemented the autopilot feature through GitHub Actions workflow and Azure Bicep, there are a couple of things to sort out for better deployment experiences.
Azure CLI doesn't currently have the ASWA deployment feature yet. It has a spec, but no implementation is yet found, which I believe the implementation will soon be available. Once it's available, we wouldn't need the GitHub PAT any longer.
Using the "Deploy to Azure" button
Azure Bicep currently supports the Deployment Scripts feature. Through this feature, we can run Azure CLI directly from the Bicep file without having to know the Azure login details. Once Azure CLI is available to deploy ASWA, this would significantly improve the deployment experience.
So far, we've implemented the autopilot feature using various GitHub Actions triggers and Azure Bicep. So now, when you need to show off your PoC to your clients, as long as anyone can access your repository, they can create resources and deploy the app by themselves without having to learn deployment details.