A while ago, I wrote a blog post about Azure DevOps Pipelines refactoring technics. GitHub Actions is also suitable for building CI/CD pipelines. But, compared to Azure DevOps, there are many spaces to achieve the same efficiency level as Azure DevOps. GitHub Actions has recently released a new feature called "Reusable Workflows", which you can reduce the refactoring concerns. Throughout this post, I'm going to refactor the existing Azure Static Web Apps CI/CD pipeline workflows, using the "reusable workflows" feature of GitHub Actions.
Workflow for Azure Static Web Apps
While provisioning an Azure Static Web Apps (ASWA) instance, GitHub Actions workflow is automatically generated by default. Here's a sample workflow. I'm pretty sure it's not that different from yours.
### original workflow: azure-static-web-apps-xxxx-xxxx-xxxx.yml ### | |
name: Azure Static Web Apps CI/CD | |
on: | |
push: | |
branches: | |
- main | |
pull_request: | |
types: [opened, synchronize, reopened, closed] | |
branches: | |
- main | |
jobs: | |
build_and_deploy_job: | |
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') | |
runs-on: ubuntu-latest | |
name: Build and Deploy Job | |
steps: | |
- uses: actions/checkout@v2 | |
with: | |
submodules: true | |
- name: Build And Deploy | |
id: builddeploy | |
uses: Azure/static-web-apps-deploy@v1 | |
with: | |
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX }} | |
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) | |
action: "upload" | |
###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### | |
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig | |
app_location: "webapp" # App source code path | |
api_location: "apiapp" # Api source code path - optional | |
output_location: "wwwroot" # Built app content directory - optional | |
###### End of Repository/Build Configurations ###### | |
close_pull_request_job: | |
if: github.event_name == 'pull_request' && github.event.action == 'closed' | |
runs-on: ubuntu-latest | |
name: Close Pull Request Job | |
steps: | |
- name: Close Pull Request | |
id: closepullrequest | |
uses: Azure/static-web-apps-deploy@v1 | |
with: | |
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX }} | |
action: "close" |
The problem is that once you provision the ASWA instance, you can't change the name of the auto-generated workflow file. Therefore, you are only allowed to modify the file.
Let's imagine a situation. You've got a codebase that deploys to multiple Azure Static Web Apps instances – DEV, TEST and PROD, which is pretty common. In that case, you will have as many GitHub Actions workflow files as the number of ASWA instances. But all the workflow files are virtually the same as each other except their filenames. So if you refactor those workflows, the overall process would be more simplified.
For the refactoring practice, you would use the workflow_dispatch
event, together with the webhook event, to call the refactored workflow. Once it's set up, unless the access token gets invalidated, you'll be able to use the workflow. But what if the access token is expired or compromised? You MUST reissue the token, which is less ideal. How can we work out this situation?
Reusable Workflows (or Called Workflow)
The reusable workflows use the newly introduced event called workflow_call
. It reuses the workflows at the job level. Let's refactor the workflow above. Copy both build_and_deploy_job
and close_pull_request_job
jobs and paste both into a new YAML file, which is called either "reusable workflow" or "called workflow".
### reusable workflow: build-aswa.yaml ### | |
name: Azure Static Web Apps | |
jobs: | |
build_and_deploy_job: | |
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') | |
runs-on: ubuntu-latest | |
name: Build and Deploy Job | |
steps: | |
- uses: actions/checkout@v2 | |
with: | |
submodules: true | |
- name: Build And Deploy | |
id: builddeploy | |
uses: Azure/static-web-apps-deploy@v1 | |
with: | |
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX }} | |
repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) | |
action: "upload" | |
###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### | |
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig | |
app_location: "webapp" # App source code path | |
api_location: "apiapp" # Api source code path - optional | |
output_location: "dist" # Built app content directory - optional | |
###### End of Repository/Build Configurations ###### | |
close_pull_request_job: | |
if: github.event_name == 'pull_request' && github.event.action == 'closed' | |
runs-on: ubuntu-latest | |
name: Close Pull Request Job | |
steps: | |
- name: Close Pull Request | |
id: closepullrequest | |
uses: Azure/static-web-apps-deploy@v1 | |
with: | |
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX }} | |
action: "close" |
According to the workflow above, there are input variables and secret variables and other variables:
-
Event Object
- Event Name:
github.event_name
- Event Action:
github.event.action
- Event Name:
-
Secrets
- Azure Static Web Apps API Token:
secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX
- GitHub Token:
secrets.GITHUB_TOKEN
- Azure Static Web Apps API Token:
-
App Locations
- Web App:
app_location
- API App:
api_location
- Web App Artifact:
output_location
- Web App:
The "called" workflow can't directly access those values defined in the "caller" workflow. Therefore, they MUST be propagated from the "caller" workflow to the "called" workflow. Let's update the variable parts in the "called" workflow like below (line #7-8, 20-21, 23-24, 30-31, 33-34, 36-37, 43-44, 54-55).
### reusable workflow: build-aswa.yaml ### | |
name: Azure Static Web Apps | |
jobs: | |
build_and_deploy_job: | |
# if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') | |
if: inputs.event_name == 'push' || (inputs.event_name == 'pull_request' && inputs.event_action != 'closed') | |
runs-on: ubuntu-latest | |
name: Build and Deploy Job | |
steps: | |
- uses: actions/checkout@v2 | |
with: | |
submodules: true | |
- name: Build And Deploy | |
id: builddeploy | |
uses: Azure/static-web-apps-deploy@v1 | |
with: | |
# azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX }} | |
azure_static_web_apps_api_token: ${{ secrets.aswa_token }} | |
# repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) | |
repo_token: ${{ secrets.github_token }} # Used for Github integrations (i.e. PR comments) | |
action: "upload" | |
###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### | |
# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig | |
# app_location: "webapp" # App source code path | |
app_location: ${{ inputs.app_location }} # App source code path | |
# api_location: "apiapp" # Api source code path - optional | |
api_location: ${{ inputs.api_location }} # Api source code path - optional | |
# output_location: "dist" # Built app content directory - optional | |
output_location: ${{ inputs.output_location }} # Built app content directory - optional | |
###### End of Repository/Build Configurations ###### | |
close_pull_request_job: | |
# if: github.event_name == 'pull_request' && github.event.action == 'closed' | |
if: inputs.event_name == 'pull_request' && inputs.event_action == 'closed' | |
runs-on: ubuntu-latest | |
name: Close Pull Request Job | |
steps: | |
- name: Close Pull Request | |
id: closepullrequest | |
uses: Azure/static-web-apps-deploy@v1 | |
with: | |
# azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX }} | |
azure_static_web_apps_api_token: ${{ secrets.aswa_token }} | |
action: "close" |
All values have become parameterised. Now, you need to define them in the "called" workflow, under the workflow_call
event. All non-secret variables go under the inputs
attribute (line #6-21), and all the secret variables go under the secrets
attribute (line #23-27).
### reusable workflow: build-aswa.yaml ### | |
name: Azure Static Web Apps | |
on: | |
workflow_call: | |
inputs: | |
event_name: | |
required: true | |
type: string | |
event_action: | |
required: true | |
type: string | |
app_location: | |
required: true | |
type: string | |
api_location: | |
required: true | |
type: string | |
output_location: | |
required: true | |
type: string | |
secrets: | |
github_token: | |
required: true | |
aswa_token: | |
required: true | |
jobs: | |
... |
Now, the refactoring has been completed!
Caller Workflow
Let's update the existing ASWA workflow. As both jobs defined under the jobs
node are no longer necessary, delete them. And define a new "reusable" workflow like below:
- Called Workflow (line #14-15):
<org_name>/<repo_name>/.github/workflows/<reusable_workflow_filename>@<branch_or_tag>
- Input Variables (line #17-22): Under
with
- Secret Variables (line #24-26): Under
secrets
### caller workflow: azure-static-web-apps-xxxx-xxxx-xxxx.yml ### | |
name: Azure Static Web Apps CI/CD | |
on: | |
push: | |
branches: | |
- main | |
pull_request: | |
types: [opened, synchronize, reopened, closed] | |
branches: | |
- main | |
jobs: | |
call_aswa_flow: | |
uses: <org_name>/<repo_name>/.github/workflows/build-aswa.yaml@main | |
with: | |
event_name: ${{ github.event_name }} | |
event_action: ${{ github.event.action }} | |
app_location: "webapp" | |
api_location: "apiapp" | |
output_location: "dist" | |
secrets: | |
github_token: ${{ secrets.GITHUB_TOKEN }} | |
aswa_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX }} |
Now, regardless of the number of "caller" workflows, update them like above. You only need to update the "called" workflow then all ASWA pipelines will get the updated workflow applied, which is really convenient!
So far, I've shown how to refactor the Azure Static Web Apps workflow with the reusable workflows feature of GitHub Actions. I'm sure that it's not just for ASWA but also can be utilised in many different scenarios.