Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create reusable workflows for prod and dev deploy of primary apps #71

Merged
merged 4 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions .github/workflows/app-build-and-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# This workflow will build a docker image, push it to ghcr.io, and deploy it to an Azure WebApp.
name: Build and Deploy to prod service app

on:
workflow_call:
secrets:
AZURE_CREDENTIALS:
description: 'Service principal that has access to the Azure apps (dev and prod) - Defined in org action secrets'
required: true
AZURE_WEBAPP_PUBLISH_PROFILE:
description: 'Publish profile for the Azure WebApp being deployed - Defined in repo action secrets'
required: true
AZURE_SECONDARY_WEBAPP_PUBLISH_PROFILE:
description: 'Publish profile for a secondary Azure WebApp if being deployed - Defined in repo action secrets'
required: false
DEPLOY_TOKEN:
description: 'Token that is used to determine if the deployment is allowed - Defined in org action secrets'
required: true
PRODUCTION_DEPLOYERS:
description: 'Name of the team that defines who can deploy to production - Defined in org action secrets'
required: true

inputs:
deploy-env:
description: 'environment to deploy (i.e. dev | prod)'
required: true
type: string
application-type:
description: 'application type (i.e. api | worker | ui) - used as a label on the Docker image'
required: true
elrayle marked this conversation as resolved.
Show resolved Hide resolved
type: string
azure-app-base-name:
description: 'Azure application name of webapp to deploy (i.e. clearlydefined-api | cdcrawler | clearlydefined)'
required: true
type: string
azure-app-name-postfix:
description: 'postfix to apply to the base name for the primary deploy site (e.g. -prod, -dev)'
required: true
type: string
secondary-azure-app-name-postfix:
description: 'postfix to apply to the base name for a secondary deploy site (e.g. -prod-europe, do not specify if no secondary site)'
type: string
default: ''
is-release:
description: 'true only when triggered by a release; otherwise false when dev deploy or manually deploy of prod'
type: boolean
default: false

jobs:
get-version:
name: Get version from package-lock.json
runs-on: ubuntu-latest
outputs:
version: ${{ env.VERSION }}
steps:
- uses: actions/[email protected]
- name: Get version from package-lock.json
id: get_version
shell: bash
run: |
version='v'$(jq -r '.version' package-lock.json) # e.g. v1.2.0
if [[ ${{ inputs.deploy-env }} == 'prod' ]]; then
if [[ ${{ inputs.is-release }} == 'true' ]]; then
# validate the version when triggered by a release
if [[ $version != 'v'${{ github.event.release.tag_name }} ]]; then
echo "Version in package-lock.json ($version) does not match the release tag (${{ github.event.release.tag_name }})"
exit 1
fi
fi
elif [[ ${{ inputs.deploy-env }} == 'dev' ]]; then
short_sha=$(echo "${{ github.sha }}" | cut -c 1-10)
version=$version'-dev-'$short_sha # e.g. v1.2.0-dev:1234567890
else
echo "Invalid deploy-env: ${{ inputs.deploy-env }}. Must be 'dev' or 'prod'"
exit 1
fi

echo "VERSION=$version" >> $GITHUB_ENV
echo "BuildAndDeploy: get-version -> outputs -> version: $version"

build-and-publish-image:
name: Build and publish Docker image
needs: get-version
uses: clearlydefined/operations/.github/workflows/app-build-docker-image.yml@elr/reusable-deploy-workflow
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
PRODUCTION_DEPLOYERS: ${{ secrets.PRODUCTION_DEPLOYERS }}
with:
deploy-env: ${{ inputs.deploy-env }}
application-type: ${{ inputs.application-type }}
image-tag: ${{ needs.get-version.outputs.version }}

deploy-primary-app-to-azure:
elrayle marked this conversation as resolved.
Show resolved Hide resolved
name: Deploy to primary Azure app
needs: [get-version, build-and-publish-image]
uses: clearlydefined/operations/.github/workflows/app-deploy-to-azure.yml@elr/reusable-deploy-workflow
secrets:
AZURE_WEBAPP_PUBLISH_PROFILE: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
PRODUCTION_DEPLOYERS: ${{ secrets.PRODUCTION_DEPLOYERS }}
with:
deploy-env: ${{ inputs.deploy-env }}
azure-webapp-name: ${{ inputs.azure-app-base-name }}${{ inputs.azure-app-name-postfix }}
application-version: ${{ needs.get-version.outputs.version }}
image-name-with-tag: ${{ needs.build-and-publish-image.outputs.docker-image-name-with-tag }}

deploy-secondary-app-to-azure:
name: Deploy to secondary Azure app
if: ${{ inputs.secondary-azure-app-name-postfix != '' }}
needs: [get-version, build-and-publish-image]
uses: clearlydefined/operations/.github/workflows/app-deploy-to-azure.yml@elr/reusable-deploy-workflow
secrets:
AZURE_WEBAPP_PUBLISH_PROFILE: ${{ secrets.AZURE_SECONDARY_WEBAPP_PUBLISH_PROFILE }}
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
PRODUCTION_DEPLOYERS: ${{ secrets.PRODUCTION_DEPLOYERS }}
with:
deploy-env: ${{ inputs.deploy-env }}
azure-webapp-name: ${{ inputs.azure-app-base-name }}${{ inputs.secondary-azure-app-name-postfix }}
application-version: ${{ needs.get-version.outputs.version }}
image-name-with-tag: ${{ needs.build-and-publish-image.outputs.docker-image-name-with-tag }}
86 changes: 86 additions & 0 deletions .github/workflows/app-build-docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# This workflow will build a docker image, push it to ghcr.io. It returns the docker image name and tag.
name: Build docker image for Azure app

on:
workflow_call:
secrets:
DEPLOY_TOKEN:
description: 'Token that is used to determine if the deployment is allowed - Defined in org action secrets'
required: true
PRODUCTION_DEPLOYERS:
description: 'Name of the team that defines who can deploy to production - Defined in org action secrets'
required: true

inputs:
deploy-env:
description: 'environment to deploy (i.e. dev | prod) - used as a label for the Docker image'
required: true
type: string
application-type:
description: 'application type (i.e. api | worker | ui) - used as a label for the Docker image'
required: true
type: string
image-tag:
description: 'the tag to use for the image (e.g. prod: v1.2.0, dev: v1.2.0+dev:1D3F567890)'
required: true
type: string

outputs:
docker-image-name-with-tag:
value: ${{ jobs.determine-image-name.outputs.docker-image-name-with-tag }}

jobs:
check-deployable:
uses: clearlydefined/operations/.github/workflows/app-is-deployable.yml@elr/reusable-deploy-workflow
with:
deploy-env: ${{ inputs.deploy-env }}
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
PRODUCTION_DEPLOYERS: ${{ secrets.PRODUCTION_DEPLOYERS }}

determine-image-name:
name: Determine Image Name
runs-on: ubuntu-latest
outputs:
docker-image-name-with-tag: ${{ env.DOCKER_IMAGE_NAME_WITH_TAG }}
steps:
- name: Determine Docker Image Name
id: determine_image_name
run: |
image_base_name=ghcr.io/${{ github.repository }} # e.g. ghcr.io/clearlydefined/service
if [[ ${{ inputs.deploy-env }} == 'prod' ]] ; then
image_name_with_tag=$image_base_name':${{ inputs.image-tag }}'
elif [[ ${{ inputs.deploy-env }} == 'dev' ]] ; then
image_name_with_tag=$image_base_name'-dev:${{ inputs.image-tag }}'
else
echo "Invalid deploy-env: ${{ inputs.deploy-env }}. Must be 'dev' or 'prod'"
exit 1
fi
echo "DOCKER_IMAGE_NAME_WITH_TAG=$image_name_with_tag" >> $GITHUB_ENV
echo "DetermineImageName: determine_image_name -> outputs -> image_name_with_tag: $image_name_with_tag"

build-docker-image:
name: Build Image
runs-on: ubuntu-latest
needs: [check-deployable, determine-image-name]
steps:
- uses: actions/[email protected]

- name: Log into ghcr registry
uses: docker/[email protected]
with:
registry: ghcr.io
username: ${{ github.actor }} # user that kicked off the action
password: ${{ secrets.GITHUB_TOKEN }} # token created when the action launched (short lived)

- name: Build and push Docker image
id: build-and-push
uses: docker/[email protected]
with:
context: .
push: true
file: Dockerfile
tags: ${{ needs.determine-image-name.outputs.docker-image-name-with-tag }}
labels: |
env=${{ inputs.deploy-env }}
type=${{ inputs.application-type }}
92 changes: 92 additions & 0 deletions .github/workflows/app-deploy-to-azure.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# This workflow will deploy a Docker image in ghcr.io to an Azure WebApp.
name: Deploy docker image to Azure WebApp

on:
workflow_call:
secrets:
AZURE_CREDENTIALS:
description: 'Service principal that has access to the Azure apps (dev and prod) - Defined in org action secrets'
required: true
AZURE_WEBAPP_PUBLISH_PROFILE:
description: 'Publish profile for the Azure WebApp being deployed - Defined in repo action secrets'
elrayle marked this conversation as resolved.
Show resolved Hide resolved
required: true
DEPLOY_TOKEN:
description: 'Token that is used to determine if the deployment is allowed - Defined in org action secrets'
required: true
PRODUCTION_DEPLOYERS:
description: 'Name of the team that defines who can deploy to production - Defined in org action secrets'
required: true

inputs:
deploy-env:
description: 'environment to deploy (i.e. dev | prod)'
required: true
type: string
azure-webapp-name:
description: 'Azure application name of application to deploy (i.e. clearlydefined-api | cdcrawler | clearlydefined)'
required: true
type: string
application-version:
description: 'the tag to use for the image (e.g. prod: v1.2.0, dev: v1.2.0+dev:1D3F567890)'
required: true
type: string
image-name-with-tag:
description: 'Docker image name with the tag (e.g. ghcr.io/clearlydefined/clearlydefined-api:v1.2.0)'
required: true
type: string

jobs:
check-deployable:
uses: clearlydefined/operations/.github/workflows/app-is-deployable.yml@elr/reusable-deploy-workflow
with:
deploy-env: ${{ inputs.deploy-env }}
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
PRODUCTION_DEPLOYERS: ${{ secrets.PRODUCTION_DEPLOYERS }}

deploy:
name: Deploy to Azure WebApp
runs-on: ubuntu-latest
needs: check-deployable
steps:
- name: Login for Azure cli commands
uses: azure/[email protected]
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: Set DOCKER configs in Azure web app
uses: azure/[email protected]
with:
app-name: ${{ inputs.azure-webapp-name }}
app-settings-json: |
[
{
"name": "DOCKER_CUSTOM_IMAGE_NAME",
"value": "${{ inputs.image-name-with-tag }}",
"slotSetting": false
},
{
"name": "DOCKER_REGISTRY_SERVER_URL",
"value": "https://ghcr.io",
"slotSetting": false
},
{
"name": "APP_VERSION",
"value": "${{ inputs.application-version }}",
"slotSetting": false
},
{
"name": "BUILD_SHA",
"value": "${{ github.sha }}",
"slotSetting": false
}
]

# v3.0.1 passes when AZURE_WEBAPP_PUBLISH_PROFILE_PROD isn't set, but should fail.
# Added secret check above to ensure it is set.
- name: Deploy to Azure WebApp
uses: azure/[email protected]
with:
app-name: ${{ inputs.azure-webapp-name }}
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
images: '${{ inputs.image-name-with-tag }}'
77 changes: 77 additions & 0 deletions .github/workflows/app-is-deployable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Deployable

on:
workflow_call:
secrets:
DEPLOY_TOKEN:
description: 'Token that is used to determine if the deployment is allowed - Defined in org action secrets'
required: true
PRODUCTION_DEPLOYERS:
description: 'Name of the team that defines who can deploy to production - Defined in org action secrets'
required: true

inputs:
deploy-env:
description: 'environment to deploy to - one of dev, prod'
required: true
type: string

jobs:
confirm-dev:
runs-on: ubuntu-latest
outputs:
is-dev: ${{ env.IS_DEV }}
steps:
# erring on the side of caution by assuming everything is prod unless it is identified as dev and ends in -dev
- id: confirm-dev
shell: bash
run: |
is_dev=false
if [[ "${{ inputs.deploy-env }}" == 'dev' ]]; then
is_dev=true
echo "Deploying to dev environment"
else
echo "Deploying to prod or UNKNOWN environment"
fi
echo "IS_DEV=$is_dev" >> $GITHUB_ENV
echo "Deployable: confirm-dev -> outputs -> is-dev: $is_dev"

deployable:
runs-on: ubuntu-latest
needs: confirm-dev
# run deployable check for anything that is NOT dev (most conservative approach)
if: ${{ needs.confirm-dev.outputs.is-dev != 'true' }}
steps:
- name: Get organization ID
run: |
org_name=${{ github.repository_owner }}
org_info=$(curl \
-H "Authorization: token ${{ secrets.DEPLOY_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/orgs/$org_name)
org_id=$(echo "$org_info" | jq .id)
echo "ORG_ID=$org_id" >> $GITHUB_ENV

- name: Check team membership
run: |
user="${{ github.actor }}"
org_id=${{ env.ORG_ID }}
org_name=${{ github.repository_owner }}

team_info=$(curl \
-H "Authorization: token ${{ secrets.DEPLOY_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/orgs/$org_name/teams)
team_id=$(echo "$team_info" | jq '.[] | select(.name=="${{ secrets.PRODUCTION_DEPLOYERS }}") | .id')

membership=$(curl \
-H "Authorization: token ${{ secrets.DEPLOY_TOKEN }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/orgs/$org_id/team/$team_id/memberships/$user)

if [[ $membership == *"active"* ]]; then
echo "$user is a member of the team"
else
echo "$user does not have permissions to deploy"
exit 1
fi