Skip to content

Familiarization with the Workshop Project

The workshop project is composed of two repositories.

Between both repositories, there are two categories of workflows we will leverage.

  • Application developer workflows
  • Platform engineering workflows

As we explore the project we will give special focus to the highlighted sections of the sources.

continuous-deployments-the-github-way

This repository models an the code base for application development.

tree -a --gitignore -I .git continuous-deployments-the-github-way

.
├── .devcontainer
   └── devcontainer.json #(1)!
├── .github
   ├── dependabot.yml #(2)!
   ├── dependency-review-config.yml #(3)!
   ├── release.yml #(4)!
   └── workflows #(5)!
       ├── 1-check-pull-requests.yaml
       ├── 2-version-changes-to-main-branch.yaml
       ├── 3-release-new-version.yaml
       └── 4-build-and-deploy.yaml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── app #(6)!
   ├── .dockerignore
   ├── Dockerfile
   ├── index.html
   ├── main.mjs
   ├── package-lock.json
   ├── package.json
   └── rollup.config.mjs
├── bicepconfig.json #(7)!
└── infrastructure #(8)!
    ├── day-0
       ├── 1-resource-group.bicep
       ├── 2-app-service.bicep
       ├── 3-application-identity-registration.bicep
       ├── 4-container-registry.bicep
       ├── main.bicep
       ├── parameters.json
       └── provision.azcli
    └── day-1
        └── oidc-config.json

  1. When you work in a codespace, the environment you are working in is created using a development container, or dev container, hosted on a virtual machine. The configuration files for a dev container are contained in a .devcontainer directory in your repository. See "Introduction to dev containers."

  2. Dependabot takes the effort out of maintaining your dependencies. You can use it to ensure that your repository automatically keeps up with the latest releases of the packages and applications it depends on. For information on the supported repositories and ecosystems, see "Dependabot supported ecosystems and repositories." You enable Dependabot version updates by checking a dependabot.yml configuration file into your repository.

  3. Dependency review helps you understand dependency changes and the security impact of these changes at every pull request. It provides an easily understandable visualization of dependency changes with a rich diff on the "Files Changed" tab of a pull request. Dependency review informs you of:

    • Which dependencies were added, removed, or updated, along with the release dates.
    • How many projects use these components.
    • Vulnerability data for these dependencies.

    For more information, see "About dependency review" and "Reviewing dependency changes in a pull request."

  4. See "Automatically generated release notes."

  5. GitHub Actions workflows can automate tasks throughout the software development lifecycle.
  6. The application source code.
  7. Bicep supports an optional configuration file named bicepconfig.json. Within this file, you can add values that customize your Bicep development experience. See "Configure your Bicep environment."
  8. The infrastructure resources template.

The Application Development Workflows

Check pull requests

.github/workflows/1-check-pull-requests.yaml
name: Check Pull Requests [Workshop]

# on:
#     pull_request:
#         branches:
#             - main

permissions:
    id-token: write
    contents: read
    checks: write
    pull-requests: write

jobs:
    check-pull-request:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout code
              uses: actions/checkout@v4

            - name: Dependency review
              uses: actions/dependency-review-action@v4
              with:
                  config-file: >-
                      ./.github/dependency-review-config.yml

            - name: Build
              working-directory: app
              run: |
                  docker build -t app .
            - name: Run
              run: |
                  docker run -d -p 8080:80 app
            - name: Health check
              run: |
                  curl \
                    --head \
                    --request GET \
                    --retry 10 \
                    --retry-delay 1 \
                    --retry-max-time 10 \
                    --retry-all-errors \
                    http://localhost:8080

            - name: Run tests
              run: |
                  curl -Ls http://localhost:8080?name=hello

Version changes to the main branch

.github/workflows/2-version-changes-to-main-branch.yaml
name: Version changes to the main branch

# on:
#     push:
#         branches:
#             - main
#     workflow_dispatch: {}

jobs:
    version-main-branch-changes:
        permissions:
            contents: read

        runs-on: ubuntu-latest

        steps:
            - uses: actions/create-github-app-token@v1
              id: generate-ci-app-token
              with:
                  app-id: ${{ vars.CI_APP_ID }}
                  private-key: ${{ secrets.CI_APP_KEY }}

            - uses: actions/checkout@v4
              with:
                  fetch-depth: 0
                  token: ${{ steps.generate-ci-app-token.outputs.token }}

            - name: Compute latest version
              id: get_latest_version
              run: |
                  # Retrieve the latest git tag, as we will only be processing one delivery line.
                  latest_version=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
                  echo "Latest version is $latest_version"
                  echo "latest_version=$latest_version" >> "$GITHUB_OUTPUT"

            - name: Compute next version
              id: get_next_version
              run: |
                  major=$(echo $latest_version | cut --delimiter=. --fields=1)
                  minor=$(echo $latest_version | cut --delimiter=. --fields=2)
                  patch=$(echo $latest_version | cut --delimiter=. --fields=3)

                  change_type=$(git log -1 --pretty=%B | grep -oP '(?<=change-type:\s).+' | xargs)
                  echo "Change type is: $change_type"

                  if [ "$change_type" == "backward-incompatible" ]; then
                    major_number=$(echo "$major" | cut --delimiter=v --fields=2)

                    major="v$((major_number+1))"
                    minor="0"
                    patch="0"
                  elif [ "$change_type" == "backward-compatible features" ]; then
                    minor=$((minor+1))
                    patch="0"
                  else
                    patch=$((patch+1))
                  fi

                  next_version="$major.$minor.$patch"
                  echo "Next version is $next_version"

                  echo "next_version_major=$major" >> "$GITHUB_OUTPUT"
                  echo "next_version_minor=$minor" >> "$GITHUB_OUTPUT"
                  echo "next_version_patch=$patch" >> "$GITHUB_OUTPUT"
                  echo "next_version=$next_version" >> "$GITHUB_OUTPUT"
              env:
                  latest_version: ${{ steps.get_latest_version.outputs.latest_version }}

            - name: Configure git user
              run: |
                  git config user.name "ci-app[bot]"
                  git config user.email "ci-app[bot]@users.noreply.github.com"

            - name: Create reproducible tag
              run: |
                  if git tag -a "$next_version" -m "$next_version"; then
                    git push --follow-tags origin "$next_version"
                  else
                    echo "Failed to create tag $next_version, does it already exist?"
                  fi
              env:
                  next_version: "${{ steps.get_next_version.outputs.next_version }}"

Release new version

.github/workflows/3-release-new-version.yaml
name: Release new version

# on:
#     push:
#         tags:
#             - "v*.*.*"

permissions:
    contents: write

jobs:
    package-delivery:
        permissions:
            contents: read

        runs-on: ubuntu-latest

        steps:
            - uses: actions/create-github-app-token@v1
              id: generate-ci-app-token
              with:
                  app-id: ${{ vars.CI_APP_ID }}
                  private-key: ${{ secrets.CI_APP_KEY }}

            - uses: actions/github-script@v7
              id: publish-release
              with:
                  github-token: ${{ steps.generate-ci-app-token.outputs.token }}
                  script: |

                      const response = await github.request(
                        'POST /repos/{owner}/{repo}/releases',
                        {
                          owner: context.repo.owner,
                          repo: context.repo.repo,
                          draft: false,
                          tag_name: '${{ github.ref }}',
                          generate_release_notes: true,
                          make_latest: 'true',
                          headers: {
                            'X-GitHub-Api-Version': '2022-11-28'
                          }
                        }
                      );

                      console.dir(response);

                      return response.data.id;

Build Docker image of application and deploy

.github/workflows/4-build-and-deploy.yaml
name: Build Docker image of application and deploy

# on:
#     release:
#         types:
#             - released

permissions:
  attestations: write
  checks: write
  contents: read
  id-token: write

jobs:
    build:
        uses: demos-by-igwejk/platform-library-workflows/.github/workflows/reusable-build.yaml@main
        with:
            image-name: app
            tag-name: ${{ github.event.release.tag_name }}
            environment: staging
            build-directory: app

    deploy:
        uses: demos-by-igwejk/platform-library-workflows/.github/workflows/reusable-deployment.yaml@main
        needs: build
        with:
            image-name: app
            image-tag: ${{ github.event.release.tag_name }}
            environment: staging

platform-library-workflows

This repository models the code base for platform engineering. In addition to providing standardization over the build and deploymnet processes in the organization, it also ensures governance of the process.

tree -a --gitignore -I .git platform-library-workflows

.
├── .devcontainer
   └── devcontainer.json
├── .github
   └── workflows
       ├── reusable-build.yaml
       └── reusable-deployment.yaml
├── LICENSE
└── README.md

Platform Engineering Workflows

Build and push image to GitHub Container Registry

.github/workflows/reusable-build.yaml
name: Build and push image to container registry

on:
  workflow_call:
    inputs:
      image-name:
        required: true
        type: string
      tag-name:
        required: true
        type: string
      environment:
        required: true
        type: string
      build-directory:
        required: true
        type: string

jobs:
  build-and-push:
    permissions:
      attestations: write
      checks: write
      contents: read
      id-token: write
    runs-on: ubuntu-latest
    environment:
      name: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ inputs.tag-name }}

      - name: Login to Azure
        uses: azure/login@v2
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

      - name: Login to Azure Container Registry
        run: az acr login --name ${{ vars.CONTAINER_REGISTRY }}

      - name: Build and push docker image
        id: container-image-build-and-push
        uses: docker/build-push-action@v6
        with:
          context: ${{ inputs.build-directory }}
          push: true
          tags: ${{ vars.CONTAINER_REGISTRY }}/${{ inputs.image-name }}:${{ inputs.tag-name }}

      - name: Generate artifact attestation
        uses: actions/attest-build-provenance@v1
        with:
          subject-name: ${{ vars.CONTAINER_REGISTRY }}/${{ inputs.image-name }}
          subject-digest: ${{ steps.container-image-build-and-push.outputs.digest }}
          push-to-registry: true

Deploy image to App Service

.github/workflows/reusable-deployment.yaml
name: Deploy image to App Service

on:
  workflow_call:
    inputs:
      environment:
        required: true
        type: string
      image-name:
        required: true
        type: string
      image-tag:
        required: true
        type: string

jobs:
  deploy:
    permissions:
      attestations: read
      id-token: write
      contents: read
      checks: write
    runs-on: ubuntu-latest
    environment:
      name: ${{ inputs.environment }}
      url: ${{ steps.deploy-azure-webapp.outputs.webapp-url }}
    steps:
      - name: Login to Azure
        uses: azure/login@v2
        with:
          client-id: ${{ vars.AZURE_CLIENT_ID }}
          tenant-id: ${{ vars.AZURE_TENANT_ID }}
          subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}

      - name: Login to Azure Container Registry
        run: az acr login --name ${{ vars.CONTAINER_REGISTRY }}

      - name: Verify attestation
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh attestation verify \
            oci://${{ vars.CONTAINER_REGISTRY }}/${{ inputs.image-name }}:${{ inputs.image-tag }} \
            -R ${{ github.repository}} \
            --signer-workflow ${{ vars.PLATFORM_LIBRARY_WORKFLOWS }}/.github/workflows/reusable-build.yaml

      - uses: azure/webapps-deploy@v3
        id: deploy-azure-webapp
        with:
          app-name: ${{ vars.APP_SERVICE_NAME }}
          images: ${{ vars.CONTAINER_REGISTRY }}/${{ inputs.image-name }}:${{ inputs.image-tag }}
          slot-name: production