Friday, 29 May 2026

Code for Creating a Multi Stage Workflow Structure with GitHub Actions - Mapping Azure DevOps Multi-Stage Pipelines to GitHub Actions - Part 3

 In the previous post "Create Multi Stage Pipeline Structure with GitHub Actions - The Layout - Mapping Azure DevOps Multi-Stage Pipelines to GitHub Actions - Part 2" we have dicussed the GitHub action workflow layout mapping to the Azure pipeline structure. The code for workflows can be structured as shown below for the pipeline requirement discussed in "Mapping Azure DevOps Multi-Stage Pipelines to GitHub Actions - Part 1". Note that we have seperated folder structure for job actions and step actions to organize the code properly in a manageable way. However, both are really composite actions in GitHub. Each action name is setup using a folder name as it must be action.yml or action.yaml for actions.


The main workflow is defined as eck_on_aks.yml. All others are reusable workflows to form the structure of the workflow. However, it is not possible to organize them into seprate stage folder etc. as organized in Azure pipeline templates due to limitation of GitHub reusable workflows must be in .github\workflows path. The above GitHub worflow structure represents below, pipeline structure of Azure pipelines (of course with limitations of GitHub actions), to achive pipeline as shown in "Mapping Azure DevOps Multi-Stage Pipelines to GitHub Actions - Part 1".

Now that we have understanding on the layout of the GitHub workflow as described in "Create Multi Stage Pipeline Structure with GitHub Actions - The Layout - Mapping Azure DevOps Multi-Stage Pipelines to GitHub Actions - Part 2" , we can explore how the code is defined in the workflow, to achive the structure.

THE ROOT WORKFLOW

The entrypoint is the root workflow .github\workflows\eck_on_aks.yml. Here we have deined the workflow name. Then we have set the trigger as manual by setting worflow_dispatch. The important thing here is the environment input. Asin Azure pipelines it is not feasible to define multi environemnt wrkflow layout in the GitHub actions workflow, which would not a neat implementation. Instead in the GitHub workflow we can create workflow by selecting the environment at the start of workflow, so that the workflow instance would ran targeting the given environment. Then we pass this environment input to the reusable workflow, env_blue_green_stages.

name: ECK on AKS

on:
  workflow_dispatch:
    inputs:
      environment:
        description: "Environment to deploy"
        required: true
        default: "dev"
        type: choice
        options:
          - dev
          - qa
          - prd-eus-001

jobs:
  deploy:
    name: "${{ inputs.environment }}"
    uses: ./.github/workflows/env_blue_green_stages.yml
    with:
      environment: ${{ inputs.environment }}


BLUE GREEN STAGES

.github\workflows\env_blue_green_stages.yml defines the stage layout of the blue green structure below for a given environment.


The reusable workflow works with a workflow call from the root workflow .github\workflows\eck_on_aks and takes in the environment as input. Then it defines the stages below with stage input varaible set according to the stage. 

  • stage_deploy
  • stage_switch
  • stage_heath_checks
  • stage_destroy

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string

jobs:
  stage_deploy:
    name: "${{ inputs.environment }}: deploy"
    uses: ./.github/workflows/stage_deploy_iac.yml
    with:
      environment: ${{ inputs.environment }}
      stage: deploy
 
  stage_switch:
    name: "${{ inputs.environment }}: switch"
    needs: stage_deploy
    uses: ./.github/workflows/stage_deploy_services.yml
    with:
      environment: ${{ inputs.environment }}
      stage: switch
 
  stage_health_checks:
    name: "${{ inputs.environment }}: health_checks"
    needs: stage_switch
    uses: ./.github/workflows/stage_run_health_checks.yml
    with:
      environment: ${{ inputs.environment }}
      stage: switch
 
  stage_destroy:
    name: "${{ inputs.environment }}: destroy"
    needs: stage_health_checks
    uses: ./.github/workflows/stage_deploy_iac.yml
    with:
      environment: ${{ inputs.environment }}
      stage: destroy

JOBS IN STAGES

Each stage described below has one or more jobs. Each job starts with a first action checkout which is required to checkout the repo in the job scope (to checkout the repo in the job runner). 

stage_deploy & stage destroy

.github\workflows\stage_deploy_iac.yml is reused in both stage_deploy and stage_destroy above. This stage_deploy_iac contains two jobs. The jobs run bound to the environment so it could use the approvals and variables defined for the environment.

The plan_iac uses two job level actions, each having the steps for the job.

  • setup_blue_green_control
  • plan_iac

The deploy_iac job has two job level actions as well

  • deploy_iac
  • run_aks_nodepool_validation (this would only run if the stage is deploy, so the destroy stage skips this job action)

The seperation of plan_iac and deploy_iac to two jobs and binding them to the environment allows approval before each job runs so the plan can be validated before applying it. Then we run the node pool validation only in the deploy stage.

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      stage:
        required: true
        type: string

jobs:
  plan_iac:
    name: "${{ inputs.stage }}: plan_iac"
    runs-on: ubuntu-24.04
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v6.0.3
     
      - uses: ./.github/actions/job/setup_blue_green_control
        with:
          environment: ${{ inputs.environment }}
          stage: ${{ inputs.stage }}
     
      - uses: ./.github/actions/job/plan_iac
        with:
          environment: ${{ inputs.environment }}
          stage: ${{ inputs.stage }}

  deploy_iac:
    name: "${{ inputs.stage }}: deploy_iac"
    runs-on: ubuntu-24.04
    needs: plan_iac
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v6.0.3
     
      - uses: ./.github/actions/job/deploy_iac
        with:
          environment: ${{ inputs.environment }}
          stage: ${{ inputs.stage }}
     
      - if: ${{ inputs.stage == 'deploy' }}
        uses: ./.github/actions/job/run_aks_nodepool_validation
        with:
          environment: ${{ inputs.environment }}
          stage: ${{ inputs.stage }}


stage_switch

This stage uses the .github\workflows\stage_deploy_services.yml to define its job deploy_services. It has two job level actions. Note that we reuse the same setup_blue_green_control here, which used in above stage_deploy and stage_destroy jobs as well. This let us manage the workflow code in more structural and clear way. 

  • setup_blue_green_control
  • deploy_service

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      stage:
        required: true
        type: string

jobs:
  deploy_services:
    name: "${{ inputs.stage }}: deploy_services"
    runs-on: ubuntu-24.04
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v6.0.3

      - uses: ./.github/actions/job/setup_blue_green_control
        with:
          environment: ${{ inputs.environment }}
          stage: ${{ inputs.stage }}

      - uses: ./.github/actions/job/deploy_services
        with:
          environment: ${{ inputs.environment }}
          stage: ${{ inputs.stage }}


stage_health_check

The health checks stage is defined in the .github\workflows\stage_run_health_checks.yml. This stage has a single job run_health_checks which has a single job level action run_health_checks.

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      stage:
        required: true
        type: string

jobs:
  run_health_check:
    name: "${{ inputs.stage }}: run_health_check"
    runs-on: ubuntu-24.04
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v6.0.3

      - uses: ./.github/actions/job/run_health_checks
        with:
          environment: ${{ inputs.environment }}
          stage: ${{ inputs.stage }}


JOB LEVEL ACTIONS

Job level actions map to Azure pipeline jobs scope. The contain multiple steps grouped together to make a meaningful job functionality.

setup_blue_green_control

.github\actions\job\setup_blue_green_control\action.yml defines the setup_blue_green_control job level action, which takes input variables environment and stage (there may be more in actual implementation, the current two inputs are suffcient to setup the structure of the workflow). This job level action reuses shared log_blue_green_parameters steps level action.

name: setup_blue_green_control
description: Setup blue-green control

inputs:
  environment:
    description: Environment name
    required: true
  stage:
    description: Stage name
    required: true

runs:
  using: composite
  steps:
    - uses: ./.github/actions/step/log_blue_green_parameters
      with:
        environment: ${{ inputs.environment }}
        stage: ${{ inputs.stage }}

    - name: "${{ inputs.stage }}: setup_blue_green_control"
      shell: bash
      run: |
        echo "Run setup blue-green control for environment: ${{ inputs.environment }}"
        echo "stage: ${{ inputs.stage }}"

plan_iac

.github\actions\job\plan_iac\action.yml defines the job level actions for plan_iac. It reuses log_blue_green_parameters step level action and then it will define the plan_iac step (there can be multiple steps requirement in actual implementation).

name: plan_iac
description: Plan IaC

inputs:
  environment:
    description: Environment name
    required: true
  stage:
    description: Stage name
    required: true

runs:
  using: composite
  steps:
    - uses: ./.github/actions/step/log_blue_green_parameters
      with:
        environment: ${{ inputs.environment }}
        stage: ${{ inputs.stage }}

    - name: "${{ inputs.stage }}: plan_iac"
      shell: bash
      run: |
        echo "Run plan IaC for environment: ${{ inputs.environment }}"
        echo "stage: ${{ inputs.stage }}"

deploy_iac

github\actions\job\deploy_iac\action.yml defines the job level actions for plan_iac. This is similar to plan_iac job level action.

name: deploy_iac
description: Deploy IaC

inputs:
  environment:
    description: Environment name
    required: true
  stage:
    description: Stage name
    required: true

runs:
  using: composite
  steps:
    - uses: ./.github/actions/step/log_blue_green_parameters
      with:
        environment: ${{ inputs.environment }}
        stage: ${{ inputs.stage }}

    - name: "${{ inputs.stage }}: deploy_iac"
      shell: bash
      run: |
        echo "Run deploy IaC for environment: ${{ inputs.environment }}"
        echo "stage: ${{ inputs.stage }}"

run_aks_nodepool_validation 

This job level action is defined in the .github\actions\job\run_aks_nodepool_validation\action.yml.

name: run_aks_nodepool_validation
description: Run AKS nodepool validation

inputs:
  environment:
    description: Environment name
    required: true
  stage:
    description: Stage name
    required: true

runs:
  using: composite
  steps:
    - uses: ./.github/actions/step/log_blue_green_parameters
      with:
        environment: ${{ inputs.environment }}
        stage: ${{ inputs.stage }}
   
    - name: "${{ inputs.stage }}: run_aks_nodepool_validation"
      shell: bash
      run: |
        echo "Run aks nodepool validation for environment: ${{ inputs.environment }}"
        echo "stage: ${{ inputs.stage }}"

deploy_services

.github\actions\job\deploy_services\action.yml defines the job levels actions for deploy services.

name: deploy_services
description: Deploy services

inputs:
  environment:
    description: Environment name
    required: true
  stage:
    description: Stage name
    required: true

runs:
  using: composite
  steps:
    - uses: ./.github/actions/step/log_blue_green_parameters
      with:
        environment: ${{ inputs.environment }}
        stage: ${{ inputs.stage }}

    - name: "${{ inputs.stage }}: deploy_services"
      shell: bash
      run: |
        echo "Run deploy services for environment: ${{ inputs.environment }}"
        echo "stage: ${{ inputs.stage }}"

run_health_checks

This job level action is defined in .github\actions\job\run_health_checks\action.yml.

name: run_health_checks
description: Run health checks

inputs:
  environment:
    description: Environment name
    required: true
  stage:
    description: Stage name
    required: true

runs:
  using: composite
  steps:
    - uses: ./.github/actions/step/log_blue_green_parameters
      with:
        environment: ${{ inputs.environment }}
        stage: ${{ inputs.stage }}
   
    - name: "${{ inputs.stage }}: run_health_checks"
      shell: bash
      run: |
        echo "Run health checks for environment: ${{ inputs.environment }}"
        echo "stage: ${{ inputs.stage }}"


STEP LEVEL ACTIONS

Step level actions contain shared steps that can be shared to multiple jobs (or job level actions). Such examples would be install terraform, install helm etc. this allows to keep terraform version defined only once in the workflow step and reuse it across all jobs.

log_blue_green_parameters

.github\actions\step\log_blue_green_parameters\action.yml has the step level actions defined.

name: log_blue_green_parameters
description: Logs blue-green paramters

inputs:
  environment:
    description: Environment name
    required: true
  stage:
    description: Stage name
    required: true

runs:
  using: composite
  steps:
    - name: "${{ inputs.stage }}: log_blue_green_parameters"
      shell: bash
      run: |        
        echo "Environment: ${{ inputs.environment }}"
        echo "Stage: ${{ inputs.stage }}"
        echo "Live AKS nodepool: just me yet"


The workflow and actions code is available here in GitHub.

No comments:

Popular Posts