Skip to content

Commit

Permalink
Merge pull request #375 from Lombiq/issue/OSOE-861
Browse files Browse the repository at this point in the history
OSOE-861: Add Git tags for Azure deployments and swaps
  • Loading branch information
Piedone authored Aug 29, 2024
2 parents 0977cd0 + d1ce2bd commit c1f2767
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
param(
[parameter(Mandatory = $true)][string]$ApplicationInsightsResourceId,
[parameter(Mandatory = $true)][string]$ReleaseName,
[parameter(Mandatory = $false)][string]$Timestamp = '',
[parameter(Mandatory = $false)]$ReleaseProperties = @()
)

Expand All @@ -9,7 +10,7 @@ Write-Output "Adding release annotation with the release name `"$ReleaseName`"."
$annotation = @{
Id = [Guid]::NewGuid()
AnnotationName = $ReleaseName
EventTime = (Get-Date).ToUniversalTime().GetDateTimeFormats('s')[0]
EventTime = if ($Timestamp) { $Timestamp } else { (Get-Date).ToUniversalTime().GetDateTimeFormats('s')[0] }
# AI only displays annotations from the "Deployment" category so this must be this string.
Category = 'Deployment'
Properties = ConvertTo-Json $ReleaseProperties -Compress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ inputs:
required: false
description: >
The name to give the created release annotation. This will be visible on the Azure Portal when viewing it.
timestamp:
required: false
description: >
The timestamp to use for the release annotation. If not set then the current time will be used. This can be useful
when using a single timestamp for multiple actions in a workflow.
commit-sha:
required: false
default: ${{ github.sha }}
description: >
The commit SHA to include in the release annotation. If not set then the current commit SHA will be used. This is
useful in case of a swap deployment where the code repository is different than the repository the action is
running in.
runs:
using: composite
Expand All @@ -28,13 +40,15 @@ runs:
# Might contain user input so should go via an env var for security.
RELEASE_NAME: ${{ inputs.release-name }}
AI_RESOURCE_ID: ${{ inputs.application-insights-resource-id }}
TIMESTAMP: ${{ inputs.timestamp }}
run: |
$params = @{
ApplicationInsightsResourceId = $Env:AI_RESOURCE_ID
ReleaseName = $Env:RELEASE_NAME ? $Env:RELEASE_NAME : 'Run #${{ github.run_number }} (GitHub Actions)'
Timestamp = $Env:TIMESTAMP
ReleaseProperties = @{
'Commit branch' = '${{ github.head_ref }}'
'Commit SHA' = '${{ github.sha }}'
'Commit SHA' = '${{ inputs.commit-sha }}'
'Workflow name' = '${{ github.workflow }}'
'Workflow run URL' = 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
}
Expand Down
6 changes: 5 additions & 1 deletion .github/actions/checkout/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ inputs:
fetch-depth:
description: Number of commits to fetch. 0 indicates all history for all branches and tags.
default: 1
submodules:
description: >
Whether to checkout submodules - `true` to checkout submodules or `recursive` to recursively checkout submodules.
default: recursive

runs:
using: composite
Expand All @@ -40,7 +44,7 @@ runs:
with:
repository: ${{ inputs.repository }}
ref: ${{ inputs.ref }}
submodules: recursive
submodules: ${{ inputs.submodules }}
token: ${{ env.CHECKOUT_TOKEN }}
path: ${{ inputs.path }}
fetch-depth: ${{ inputs.fetch-depth }}
25 changes: 25 additions & 0 deletions .github/actions/create-timestamp/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Create Timestamp
description: >
Create a timestamp in ISO 8601 format for use in tags (replaces ":" with "." for use in git tags). Intentionally not
documented in Actions.md since it's only meant for internal use.
runs:
using: composite
steps:
- name: Get Timestamp
id: get-timestamp
shell: pwsh
run: |
# Get the current timestamp in the ISO 8601 format but replace ":" with "." because we can't use ":" in tags.
$timestamp = ((Get-Date).ToUniversalTime().GetDateTimeFormats('s')[0])
$timestampTag = $timestamp -replace ":", "-"
echo "timestamp=$timestamp" >> $env:GITHUB_OUTPUT
echo "timestamp-tag=$timestampTag" >> $env:GITHUB_OUTPUT
outputs:
timestamp:
description: The generated timestamp.
value: ${{ steps.get-timestamp.outputs.timestamp }}
timestamp-tag:
description: The generated timestamp parsed as a usable git tag.
value: ${{ steps.get-timestamp.outputs.timestamp-tag }}
19 changes: 17 additions & 2 deletions .github/actions/release-action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ description: >
Runs ncipollo/release-action. Exists only to centralize which version of the action we use. Intentionally not
documented in Actions.md since it's only meant for internal use.
# Copied from https://github.com/ncipollo/release-action/blob/v1.11.2/action.yml. Formatted to wrap long
# descriptions. Removed inputs not used by Lombiq GitHub-Actions.
# Copied from https://github.com/ncipollo/release-action/blob/main/action.yml. Formatted to wrap long descriptions.
# Removed inputs not used by Lombiq GitHub-Actions.
inputs:
allowUpdates:
description: >
Expand All @@ -25,6 +25,18 @@ inputs:
description: An optional tag for the release. If this is omitted the git ref will be used (if it is a tag).
required: false
default: ''
repo:
description: Optionally specify the repo where the release should be generated. Defaults to current repo.
required: false
default: ''
token:
description: The GitHub token.
required: false
default: ${{ github.token }}
releaseName:
description: An optional name for the release. If this is omitted the tag will be used.
required: false
default: ''

outputs:
id:
Expand All @@ -49,3 +61,6 @@ runs:
artifacts: ${{ inputs.artifacts }}
generateReleaseNotes: ${{ inputs.generateReleaseNotes }}
tag: ${{ inputs.tag }}
repo: ${{ inputs.repo }}
token: ${{ inputs.token }}
name: ${{ inputs.releaseName }}
25 changes: 25 additions & 0 deletions .github/actions/remove-old-latest-tags/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Remove Old Latest Tags
description: >
Removes the old prefix/latest tag so that a new one can be set. Intentionally not documented in Actions.md since
it's only meant for internal use.
inputs:
tag-prefix:
description: The prefix for the tag we are removing.
required: true

runs:
using: composite
steps:
- name: Delete Latest Tag
shell: pwsh
run: |
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git config user.name 'github-actions[bot]'
git fetch --tags
$latestTag = (git tag --list '${{ inputs.tag-prefix }}/latest')
if ($latestTag)
{
git tag --delete '${{ inputs.tag-prefix }}/latest'
git push origin ':refs/tags/${{ inputs.tag-prefix }}/latest'
}
25 changes: 25 additions & 0 deletions .github/workflows/deploy-to-azure-app-service.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ on:
description: >
Compiles application assemblies as ReadyToRun (R2R) format. R2R is a form of ahead-of-time (AOT) compilation.
If ready to run is set to `true` the `runtime` input is needed.
tag-prefix:
type: string
default: staging
required: false
description: >
The prefix to use for the tag. If not set, 'staging' will be used as the prefix. The tag will be in the
format of "<prefix>/latest" and "<prefix>/<timestamp>". This is used for tracking what's deployed, where and
when by tagging the deployed commit.
jobs:
deploy:
Expand Down Expand Up @@ -227,11 +235,28 @@ jobs:
restart: false
clean: false

- name: Create Timestamp
id: create-timestamp
uses: Lombiq/GitHub-Actions/.github/actions/create-timestamp@dev

- name: Add Azure Application Insights Release Annotation
uses: Lombiq/GitHub-Actions/.github/actions/add-azure-application-insights-release-annotation@dev
with:
release-name: 'Deploy #${{ github.run_number }} to ${{ inputs.slot-name }}'
application-insights-resource-id: ${{ inputs.application-insights-resource-id }}
timestamp: ${{ steps.create-timestamp.outputs.timestamp }}

- name: Remove Old Latest Tags
uses: Lombiq/GitHub-Actions/.github/actions/remove-old-latest-tags@dev
with:
tag-prefix: ${{ inputs.tag-prefix }}

- name: Tag Latest and Add Timestamp Tag
run: |
git tag '${{ inputs.tag-prefix }}/latest'
git push origin 'refs/tags/${{ inputs.tag-prefix }}/latest'
git tag '${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}'
git push origin 'refs/tags/${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}'
- name: Start Web App Slot
run: |
Expand Down
110 changes: 105 additions & 5 deletions .github/workflows/swap-azure-web-app-slots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ on:
The ID of the Azure Subscription the resources are under, which will be mapped to the subscription-id
parameter when calling azure/login. You can look this up e.g. in the Azure Portal under any resource or the
subscription itself.
CODE_REPOSITORY_WRITE_TOKEN:
required: false
description: >
An authentication token, like a personal access token (PAT) that gives access to the other repository.
This is necessary because the repository that triggered the workflow and the repository that the workflow is
running on are different in this case.
inputs:
cancel-workflow-on-failure:
Expand Down Expand Up @@ -69,6 +75,30 @@ on:
description: >
ID of the Application Insights resource that the release annotation for the swap should be added to.
This can e.g. be looked up on the Azure Portal under the given AI resource's Overview page -> JSON View.
app-code-repository:
type: string
default: ''
required: false
description: >
The repository that hosts the code that will be swapped during the Azure Web App slot swap process. This is
necessary for adding tags to the repository to track the deployment status and history. If not specified,
tagging steps will be skipped. Example input: 'organization/repository-name'
tag-prefix:
type: string
default: production
required: false
description: >
The prefix for the tags that will be added to the app-code-repository. If not set, 'production' will be used
as the prefix. The tag will be in the format of "<prefix>/latest" and "<prefix>/<timestamp>". This is used for
tracking what's deployed, where and when by tagging the deployed commit.
swap-prefix:
type: string
default: staging
required: false
description: >
The prefix of the deployment tag that we will be looking for in the app-code-repository. If not set, 'staging'
will be used as the prefix. This is used to find the currently deployed commit by its tag in the
app-code-repository.
jobs:
swap-azure-web-app-slots:
Expand Down Expand Up @@ -105,11 +135,9 @@ jobs:
-SourceSlotName ${{ inputs.source-slot-name }} `
-DestinationSlotName ${{ inputs.destination-slot-name }}
- name: Add Azure Application Insights Release Annotation
uses: Lombiq/GitHub-Actions/.github/actions/add-azure-application-insights-release-annotation@dev
with:
release-name: 'Swap #${{ github.run_number }} from ${{ inputs.source-slot-name }} to ${{ inputs.destination-slot-name }}'
application-insights-resource-id: ${{ inputs.application-insights-resource-id }}
- name: Create Timestamp
id: create-timestamp
uses: Lombiq/GitHub-Actions/.github/actions/create-timestamp@dev

- name: Test Destination Web App Slot
run: |
Expand All @@ -118,6 +146,78 @@ jobs:
-WebAppName ${{ inputs.app-name }} `
-SlotName ${{ inputs.destination-slot-name }}
- name: Checkout Code Repository
if: inputs.app-code-repository != ''
uses: Lombiq/GitHub-Actions/.github/actions/checkout@dev
with:
repository: ${{ inputs.app-code-repository }}
token: ${{ secrets.CODE_REPOSITORY_WRITE_TOKEN }}
submodules: false

- name: Remove Old Latest Tags
uses: Lombiq/GitHub-Actions/.github/actions/remove-old-latest-tags@dev
with:
tag-prefix: ${{ inputs.tag-prefix }}

- name: Move Latest Tag (Swap) and Add Timestamp Tag
id: move-latest-tag
if: inputs.app-code-repository != ''
run: |
if([string]::IsNullOrEmpty('${{ inputs.tag-prefix }}'))
{
throw 'Tag prefix not set, exiting'
}
$tagExists = (git tag --list '${{ inputs.swap-prefix }}/latest')
if ([string]::IsNullOrEmpty($tagExists))
{
throw 'No latest tag found for swap prefix'
}
$latest = (git rev-list -n 1 '${{ inputs.swap-prefix }}/latest')
if ($latest)
{
git tag --annotate '${{ inputs.tag-prefix }}/latest' $latest --message 'Latest tag for ${{ inputs.tag-prefix }}'
git push origin 'refs/tags/${{ inputs.tag-prefix }}/latest'
$gitMessage = 'Swap tagged at ${{ steps.create-timestamp.outputs.timestamp-tag }}'
git tag --annotate '${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}' $latest --message $gitMessage
git push origin 'refs/tags/${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}'
}
else
{
throw "No latest tag found for swap prefix"
}
Write-Output "::notice::The commit from the app code repository with the SHA $latest was swapped out."
"commit-sha=$latest" >> $Env:GITHUB_OUTPUT
- name: Add Azure Application Insights Release Annotation
uses: Lombiq/GitHub-Actions/.github/actions/add-azure-application-insights-release-annotation@dev
with:
release-name: 'Swap #${{ github.run_number }} from ${{ inputs.source-slot-name }} to ${{ inputs.destination-slot-name }}'
application-insights-resource-id: ${{ inputs.application-insights-resource-id }}
timestamp: ${{ steps.create-timestamp.outputs.timestamp }}
commit-sha: ${{ steps.move-latest-tag.outputs.commit-sha }}

# Required because the release action takes the repository name and owner as separate inputs.
- name: Extract Repository Name
id: extract-repo-name
if: inputs.app-code-repository != ''
run: |
$repoName = '${{ inputs.app-code-repository }}'.Split('/')[1]
"repo-name=$repoName" >> $Env:GITHUB_OUTPUT
- name: Create Release
uses: Lombiq/GitHub-Actions/.github/actions/release-action@dev
if: inputs.app-code-repository != ''
with:
repo: ${{ steps.extract-repo-name.outputs.repo-name }}
token: ${{ secrets.CODE_REPOSITORY_WRITE_TOKEN }}
tag: ${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}
releaseName: ${{ steps.create-timestamp.outputs.timestamp }}
allowUpdates: true
generateReleaseNotes: true

- name: Cancel Workflow on Failure
if: failure() && inputs.cancel-workflow-on-failure == 'true'
uses: Lombiq/GitHub-Actions/.github/actions/cancel-workflow@dev
Expand Down
2 changes: 2 additions & 0 deletions Docs/Workflows/AzureHosting/DeployToAzureAppService.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
self-contained: true
ready-to-run: true
application-insights-resource-id: "Azure resource ID of the corresponding AI resource"
# Defaults to 'staging' if not set, used for adding git tags to the deployed commit.
tag-prefix: staging
secrets:
AZURE_APP_SERVICE_DEPLOYMENT_SERVICE_PRINCIPAL_ID: ${{ secrets.AZURE_APP_SERVICE_DEPLOYMENT_SERVICE_PRINCIPAL_ID }}
AZURE_APP_SERVICE_DEPLOYMENT_AZURE_TENANT_ID: ${{ secrets.AZURE_APP_SERVICE_DEPLOYMENT_AZURE_TENANT_ID }}
Expand Down
8 changes: 8 additions & 0 deletions Docs/Workflows/AzureHosting/SwapAzureWebAppSlots.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@ jobs:
source-slot-name: Staging
destination-slot-name: Production
application-insights-resource-id: "Azure resource ID of the corresponding AI resource"
# Defaults to 'production' if not set, used for adding git tags to the deployed commit.
tag-prefix: production
# Defaults to 'staging' if not set, used looking up the currently deployed commit by tag.
swap-prefix: staging
# The repository that hosts the code that will be swapped. This is necessary for adding tags and releases.
app-code-repository: Lombiq/Lombiq-GitHub-Actions
secrets:
AZURE_APP_SERVICE_SWAP_SERVICE_PRINCIPAL_ID: ${{ secrets.AZURE_APP_SERVICE_SWAP_SERVICE_PRINCIPAL_ID }}
AZURE_APP_SERVICE_SWAP_AZURE_TENANT_ID: ${{ secrets.AZURE_APP_SERVICE_SWAP_AZURE_TENANT_ID }}
AZURE_APP_SERVICE_SWAP_AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_APP_SERVICE_SWAP_AZURE_SUBSCRIPTION_ID }}
# Needs to have access to create tags and releases in the code repository.
CODE_REPOSITORY_WRITE_TOKEN: ${{ secrets.CODE_REPOSITORY_WRITE_TOKEN }}
```
To restrict who can edit or run the Swap workflow, we recommend putting into a separate repository independent of your application code. If you're [on the Enterprise plan, you can add required reviewers](https://github.com/orgs/community/discussions/26262) instead, so that not everyone is able to run a swap who has write access to the repository.
Expand Down

0 comments on commit c1f2767

Please sign in to comment.