Full workflow code that manages a separate set of tags and releases for each environment.

In the hart of it a https://github.com/PaulHatch/semantic-version is used to determine semantic versioning.

name: Terraform Apply
 
on:
  push:
    branches:
      - main
      - qa
      - dev
 
env:
  PYTHONDONTWRITEBYTECODE: 1 # Prevents Python from writing pyc files
 
jobs:
  terraform_apply:
    name: Apply
    runs-on: ubuntu-latest
    # Dynamically set the environment based on the branch name
    environment:
      name: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'qa' && 'qa' || github.ref_name == 'dev' && 'dev' }}
 
    steps:
      - name: Check out code
        uses: actions/checkout@v3
        with:
          fetch-depth: 0 # Fetches all history for all tags and branches
 
      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v2
        with:
          terraform_wrapper: false # https://stackoverflow.com/a/69927019/12451227
 
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ vars.AWS_REGION }}
 
      # Needed to setup Python for Lambda packaging when applying Terraform
      - uses: actions/setup-python@v4
        with:
          python-version: "3.11"
 
      - name: Terraform Init
        run: make init
        env:
          TF_BACKEND_BUCKET: ${{ vars.TERRAFORM_STATE_BUCKET_NAME }}
          TF_BACKEND_TABLE: ${{ vars.TERRAFORM_STATE_DYNAMODB_TABLE }}
          AWS_REGION: ${{ vars.AWS_REGION }}
 
      - name: Terraform Apply
        run: make apply
        env:
          TDP_TOKEN: ${{ secrets.TDP_TOKEN }}
          TF_VAR_FILE: envs/${{ vars.ENVIRONMENT }}.tfvars
          TF_WORKSPACE: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'qa' && 'qa' || github.ref_name == 'dev' && 'dev' }}
          TF_LAMBDA_PACKAGE_LOG_LEVEL: WARNING # This will suppress the output of the Lambda package logs
          TETRA_CONFIG_BACKUP_BUCKET: ${{ vars.TETRA_CONFIG_BACKUP_BUCKET }}
          AUTO_APPROVE: true
 
      - name: Determine new version
        id: config_version
        uses: paulhatch/semantic-version@v5.1.0
        with:
          change_path: "config"
          namespace: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'qa' && 'qa' || github.ref_name == 'dev' && 'dev' }}-config
 
      - name: Create release tag
        if: ${{ steps.config_version.outputs.changed == 'true' }} # `.changed` returns string values `true` or `false
        uses: actions/github-script@v5
        with:
          script: |
            github.rest.git.createRef({
              owner: context.repo.owner,
              repo: context.repo.repo,
              ref: 'refs/tags/${{ steps.config_version.outputs.version_tag }}',
              sha: context.sha
            })
 
      - name: Fetch Terraform output
        if: ${{ steps.config_version.outputs.changed == 'true' }} # `.changed` returns string values `true` or `false
        id: terraform_output
        # https://trstringer.com/github-actions-multiline-strings/
        run: |
          OUTPUT=$(cd infra && terraform output)
          echo "TF_OUTPUT<<EOF" >> $GITHUB_ENV
          echo "$OUTPUT" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV
        env:
          TF_WORKSPACE: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'qa' && 'qa' || github.ref_name == 'dev' && 'dev' }}
 
      - name: Create release
        if: ${{ steps.config_version.outputs.changed == 'true' }} # `.changed` returns string values `true` or `false
        uses: actions/github-script@v5
        with:
          script: |
            github.rest.repos.createRelease({
              owner: context.repo.owner,
              repo: context.repo.repo,
              tag_name: '${{ steps.config_version.outputs.version_tag }}',
              name: '${{ steps.config_version.outputs.version_tag }}',
              body: '```\n${{ toJson(env.TF_OUTPUT) }}\n```',
              draft: false,
              prerelease: false
            })