Friday, December 3, 2021

Refactoring Azure Static Web Apps CI/CD Pipeline

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
  • Secrets
    • Azure Static Web Apps API Token: secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_XXXX_XXXX_XXXX
    • GitHub Token: secrets.GITHUB_TOKEN
  • App Locations
    • Web App: app_location
    • API App: api_location
    • Web App Artifact: output_location

 

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.

 

This article was originally published on Dev Kimchi.

Posted at https://sl.advdat.com/3EzobgE