Skip to content

Commit c1f2767

Browse files
authored
Merge pull request #375 from Lombiq/issue/OSOE-861
OSOE-861: Add Git tags for Azure deployments and swaps
2 parents 0977cd0 + d1ce2bd commit c1f2767

File tree

10 files changed

+229
-10
lines changed

10 files changed

+229
-10
lines changed

.github/actions/add-azure-application-insights-release-annotation/Add-ReleaseAnnotation.ps1

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
param(
22
[parameter(Mandatory = $true)][string]$ApplicationInsightsResourceId,
33
[parameter(Mandatory = $true)][string]$ReleaseName,
4+
[parameter(Mandatory = $false)][string]$Timestamp = '',
45
[parameter(Mandatory = $false)]$ReleaseProperties = @()
56
)
67

@@ -9,7 +10,7 @@ Write-Output "Adding release annotation with the release name `"$ReleaseName`"."
910
$annotation = @{
1011
Id = [Guid]::NewGuid()
1112
AnnotationName = $ReleaseName
12-
EventTime = (Get-Date).ToUniversalTime().GetDateTimeFormats('s')[0]
13+
EventTime = if ($Timestamp) { $Timestamp } else { (Get-Date).ToUniversalTime().GetDateTimeFormats('s')[0] }
1314
# AI only displays annotations from the "Deployment" category so this must be this string.
1415
Category = 'Deployment'
1516
Properties = ConvertTo-Json $ReleaseProperties -Compress

.github/actions/add-azure-application-insights-release-annotation/action.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ inputs:
1313
required: false
1414
description: >
1515
The name to give the created release annotation. This will be visible on the Azure Portal when viewing it.
16+
timestamp:
17+
required: false
18+
description: >
19+
The timestamp to use for the release annotation. If not set then the current time will be used. This can be useful
20+
when using a single timestamp for multiple actions in a workflow.
21+
commit-sha:
22+
required: false
23+
default: ${{ github.sha }}
24+
description: >
25+
The commit SHA to include in the release annotation. If not set then the current commit SHA will be used. This is
26+
useful in case of a swap deployment where the code repository is different than the repository the action is
27+
running in.
1628
1729
runs:
1830
using: composite
@@ -28,13 +40,15 @@ runs:
2840
# Might contain user input so should go via an env var for security.
2941
RELEASE_NAME: ${{ inputs.release-name }}
3042
AI_RESOURCE_ID: ${{ inputs.application-insights-resource-id }}
43+
TIMESTAMP: ${{ inputs.timestamp }}
3144
run: |
3245
$params = @{
3346
ApplicationInsightsResourceId = $Env:AI_RESOURCE_ID
3447
ReleaseName = $Env:RELEASE_NAME ? $Env:RELEASE_NAME : 'Run #${{ github.run_number }} (GitHub Actions)'
48+
Timestamp = $Env:TIMESTAMP
3549
ReleaseProperties = @{
3650
'Commit branch' = '${{ github.head_ref }}'
37-
'Commit SHA' = '${{ github.sha }}'
51+
'Commit SHA' = '${{ inputs.commit-sha }}'
3852
'Workflow name' = '${{ github.workflow }}'
3953
'Workflow run URL' = 'https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}'
4054
}

.github/actions/checkout/action.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ inputs:
2222
fetch-depth:
2323
description: Number of commits to fetch. 0 indicates all history for all branches and tags.
2424
default: 1
25+
submodules:
26+
description: >
27+
Whether to checkout submodules - `true` to checkout submodules or `recursive` to recursively checkout submodules.
28+
default: recursive
2529

2630
runs:
2731
using: composite
@@ -40,7 +44,7 @@ runs:
4044
with:
4145
repository: ${{ inputs.repository }}
4246
ref: ${{ inputs.ref }}
43-
submodules: recursive
47+
submodules: ${{ inputs.submodules }}
4448
token: ${{ env.CHECKOUT_TOKEN }}
4549
path: ${{ inputs.path }}
4650
fetch-depth: ${{ inputs.fetch-depth }}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Create Timestamp
2+
description: >
3+
Create a timestamp in ISO 8601 format for use in tags (replaces ":" with "." for use in git tags). Intentionally not
4+
documented in Actions.md since it's only meant for internal use.
5+
6+
runs:
7+
using: composite
8+
steps:
9+
- name: Get Timestamp
10+
id: get-timestamp
11+
shell: pwsh
12+
run: |
13+
# Get the current timestamp in the ISO 8601 format but replace ":" with "." because we can't use ":" in tags.
14+
$timestamp = ((Get-Date).ToUniversalTime().GetDateTimeFormats('s')[0])
15+
$timestampTag = $timestamp -replace ":", "-"
16+
echo "timestamp=$timestamp" >> $env:GITHUB_OUTPUT
17+
echo "timestamp-tag=$timestampTag" >> $env:GITHUB_OUTPUT
18+
19+
outputs:
20+
timestamp:
21+
description: The generated timestamp.
22+
value: ${{ steps.get-timestamp.outputs.timestamp }}
23+
timestamp-tag:
24+
description: The generated timestamp parsed as a usable git tag.
25+
value: ${{ steps.get-timestamp.outputs.timestamp-tag }}

.github/actions/release-action/action.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ description: >
33
Runs ncipollo/release-action. Exists only to centralize which version of the action we use. Intentionally not
44
documented in Actions.md since it's only meant for internal use.
55
6-
# Copied from https://github.com/ncipollo/release-action/blob/v1.11.2/action.yml. Formatted to wrap long
7-
# descriptions. Removed inputs not used by Lombiq GitHub-Actions.
6+
# Copied from https://github.com/ncipollo/release-action/blob/main/action.yml. Formatted to wrap long descriptions.
7+
# Removed inputs not used by Lombiq GitHub-Actions.
88
inputs:
99
allowUpdates:
1010
description: >
@@ -25,6 +25,18 @@ inputs:
2525
description: An optional tag for the release. If this is omitted the git ref will be used (if it is a tag).
2626
required: false
2727
default: ''
28+
repo:
29+
description: Optionally specify the repo where the release should be generated. Defaults to current repo.
30+
required: false
31+
default: ''
32+
token:
33+
description: The GitHub token.
34+
required: false
35+
default: ${{ github.token }}
36+
releaseName:
37+
description: An optional name for the release. If this is omitted the tag will be used.
38+
required: false
39+
default: ''
2840

2941
outputs:
3042
id:
@@ -49,3 +61,6 @@ runs:
4961
artifacts: ${{ inputs.artifacts }}
5062
generateReleaseNotes: ${{ inputs.generateReleaseNotes }}
5163
tag: ${{ inputs.tag }}
64+
repo: ${{ inputs.repo }}
65+
token: ${{ inputs.token }}
66+
name: ${{ inputs.releaseName }}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Remove Old Latest Tags
2+
description: >
3+
Removes the old prefix/latest tag so that a new one can be set. Intentionally not documented in Actions.md since
4+
it's only meant for internal use.
5+
6+
inputs:
7+
tag-prefix:
8+
description: The prefix for the tag we are removing.
9+
required: true
10+
11+
runs:
12+
using: composite
13+
steps:
14+
- name: Delete Latest Tag
15+
shell: pwsh
16+
run: |
17+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
18+
git config user.name 'github-actions[bot]'
19+
git fetch --tags
20+
$latestTag = (git tag --list '${{ inputs.tag-prefix }}/latest')
21+
if ($latestTag)
22+
{
23+
git tag --delete '${{ inputs.tag-prefix }}/latest'
24+
git push origin ':refs/tags/${{ inputs.tag-prefix }}/latest'
25+
}

.github/workflows/deploy-to-azure-app-service.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,14 @@ on:
132132
description: >
133133
Compiles application assemblies as ReadyToRun (R2R) format. R2R is a form of ahead-of-time (AOT) compilation.
134134
If ready to run is set to `true` the `runtime` input is needed.
135+
tag-prefix:
136+
type: string
137+
default: staging
138+
required: false
139+
description: >
140+
The prefix to use for the tag. If not set, 'staging' will be used as the prefix. The tag will be in the
141+
format of "<prefix>/latest" and "<prefix>/<timestamp>". This is used for tracking what's deployed, where and
142+
when by tagging the deployed commit.
135143
136144
jobs:
137145
deploy:
@@ -227,11 +235,28 @@ jobs:
227235
restart: false
228236
clean: false
229237

238+
- name: Create Timestamp
239+
id: create-timestamp
240+
uses: Lombiq/GitHub-Actions/.github/actions/create-timestamp@dev
241+
230242
- name: Add Azure Application Insights Release Annotation
231243
uses: Lombiq/GitHub-Actions/.github/actions/add-azure-application-insights-release-annotation@dev
232244
with:
233245
release-name: 'Deploy #${{ github.run_number }} to ${{ inputs.slot-name }}'
234246
application-insights-resource-id: ${{ inputs.application-insights-resource-id }}
247+
timestamp: ${{ steps.create-timestamp.outputs.timestamp }}
248+
249+
- name: Remove Old Latest Tags
250+
uses: Lombiq/GitHub-Actions/.github/actions/remove-old-latest-tags@dev
251+
with:
252+
tag-prefix: ${{ inputs.tag-prefix }}
253+
254+
- name: Tag Latest and Add Timestamp Tag
255+
run: |
256+
git tag '${{ inputs.tag-prefix }}/latest'
257+
git push origin 'refs/tags/${{ inputs.tag-prefix }}/latest'
258+
git tag '${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}'
259+
git push origin 'refs/tags/${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}'
235260
236261
- name: Start Web App Slot
237262
run: |

.github/workflows/swap-azure-web-app-slots.yml

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ on:
2929
The ID of the Azure Subscription the resources are under, which will be mapped to the subscription-id
3030
parameter when calling azure/login. You can look this up e.g. in the Azure Portal under any resource or the
3131
subscription itself.
32+
CODE_REPOSITORY_WRITE_TOKEN:
33+
required: false
34+
description: >
35+
An authentication token, like a personal access token (PAT) that gives access to the other repository.
36+
This is necessary because the repository that triggered the workflow and the repository that the workflow is
37+
running on are different in this case.
3238
3339
inputs:
3440
cancel-workflow-on-failure:
@@ -69,6 +75,30 @@ on:
6975
description: >
7076
ID of the Application Insights resource that the release annotation for the swap should be added to.
7177
This can e.g. be looked up on the Azure Portal under the given AI resource's Overview page -> JSON View.
78+
app-code-repository:
79+
type: string
80+
default: ''
81+
required: false
82+
description: >
83+
The repository that hosts the code that will be swapped during the Azure Web App slot swap process. This is
84+
necessary for adding tags to the repository to track the deployment status and history. If not specified,
85+
tagging steps will be skipped. Example input: 'organization/repository-name'
86+
tag-prefix:
87+
type: string
88+
default: production
89+
required: false
90+
description: >
91+
The prefix for the tags that will be added to the app-code-repository. If not set, 'production' will be used
92+
as the prefix. The tag will be in the format of "<prefix>/latest" and "<prefix>/<timestamp>". This is used for
93+
tracking what's deployed, where and when by tagging the deployed commit.
94+
swap-prefix:
95+
type: string
96+
default: staging
97+
required: false
98+
description: >
99+
The prefix of the deployment tag that we will be looking for in the app-code-repository. If not set, 'staging'
100+
will be used as the prefix. This is used to find the currently deployed commit by its tag in the
101+
app-code-repository.
72102
73103
jobs:
74104
swap-azure-web-app-slots:
@@ -105,11 +135,9 @@ jobs:
105135
-SourceSlotName ${{ inputs.source-slot-name }} `
106136
-DestinationSlotName ${{ inputs.destination-slot-name }}
107137
108-
- name: Add Azure Application Insights Release Annotation
109-
uses: Lombiq/GitHub-Actions/.github/actions/add-azure-application-insights-release-annotation@dev
110-
with:
111-
release-name: 'Swap #${{ github.run_number }} from ${{ inputs.source-slot-name }} to ${{ inputs.destination-slot-name }}'
112-
application-insights-resource-id: ${{ inputs.application-insights-resource-id }}
138+
- name: Create Timestamp
139+
id: create-timestamp
140+
uses: Lombiq/GitHub-Actions/.github/actions/create-timestamp@dev
113141

114142
- name: Test Destination Web App Slot
115143
run: |
@@ -118,6 +146,78 @@ jobs:
118146
-WebAppName ${{ inputs.app-name }} `
119147
-SlotName ${{ inputs.destination-slot-name }}
120148
149+
- name: Checkout Code Repository
150+
if: inputs.app-code-repository != ''
151+
uses: Lombiq/GitHub-Actions/.github/actions/checkout@dev
152+
with:
153+
repository: ${{ inputs.app-code-repository }}
154+
token: ${{ secrets.CODE_REPOSITORY_WRITE_TOKEN }}
155+
submodules: false
156+
157+
- name: Remove Old Latest Tags
158+
uses: Lombiq/GitHub-Actions/.github/actions/remove-old-latest-tags@dev
159+
with:
160+
tag-prefix: ${{ inputs.tag-prefix }}
161+
162+
- name: Move Latest Tag (Swap) and Add Timestamp Tag
163+
id: move-latest-tag
164+
if: inputs.app-code-repository != ''
165+
run: |
166+
if([string]::IsNullOrEmpty('${{ inputs.tag-prefix }}'))
167+
{
168+
throw 'Tag prefix not set, exiting'
169+
}
170+
171+
$tagExists = (git tag --list '${{ inputs.swap-prefix }}/latest')
172+
if ([string]::IsNullOrEmpty($tagExists))
173+
{
174+
throw 'No latest tag found for swap prefix'
175+
}
176+
177+
$latest = (git rev-list -n 1 '${{ inputs.swap-prefix }}/latest')
178+
if ($latest)
179+
{
180+
git tag --annotate '${{ inputs.tag-prefix }}/latest' $latest --message 'Latest tag for ${{ inputs.tag-prefix }}'
181+
git push origin 'refs/tags/${{ inputs.tag-prefix }}/latest'
182+
$gitMessage = 'Swap tagged at ${{ steps.create-timestamp.outputs.timestamp-tag }}'
183+
git tag --annotate '${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}' $latest --message $gitMessage
184+
git push origin 'refs/tags/${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}'
185+
}
186+
else
187+
{
188+
throw "No latest tag found for swap prefix"
189+
}
190+
191+
Write-Output "::notice::The commit from the app code repository with the SHA $latest was swapped out."
192+
"commit-sha=$latest" >> $Env:GITHUB_OUTPUT
193+
194+
- name: Add Azure Application Insights Release Annotation
195+
uses: Lombiq/GitHub-Actions/.github/actions/add-azure-application-insights-release-annotation@dev
196+
with:
197+
release-name: 'Swap #${{ github.run_number }} from ${{ inputs.source-slot-name }} to ${{ inputs.destination-slot-name }}'
198+
application-insights-resource-id: ${{ inputs.application-insights-resource-id }}
199+
timestamp: ${{ steps.create-timestamp.outputs.timestamp }}
200+
commit-sha: ${{ steps.move-latest-tag.outputs.commit-sha }}
201+
202+
# Required because the release action takes the repository name and owner as separate inputs.
203+
- name: Extract Repository Name
204+
id: extract-repo-name
205+
if: inputs.app-code-repository != ''
206+
run: |
207+
$repoName = '${{ inputs.app-code-repository }}'.Split('/')[1]
208+
"repo-name=$repoName" >> $Env:GITHUB_OUTPUT
209+
210+
- name: Create Release
211+
uses: Lombiq/GitHub-Actions/.github/actions/release-action@dev
212+
if: inputs.app-code-repository != ''
213+
with:
214+
repo: ${{ steps.extract-repo-name.outputs.repo-name }}
215+
token: ${{ secrets.CODE_REPOSITORY_WRITE_TOKEN }}
216+
tag: ${{ inputs.tag-prefix }}/${{ steps.create-timestamp.outputs.timestamp-tag }}
217+
releaseName: ${{ steps.create-timestamp.outputs.timestamp }}
218+
allowUpdates: true
219+
generateReleaseNotes: true
220+
121221
- name: Cancel Workflow on Failure
122222
if: failure() && inputs.cancel-workflow-on-failure == 'true'
123223
uses: Lombiq/GitHub-Actions/.github/actions/cancel-workflow@dev

Docs/Workflows/AzureHosting/DeployToAzureAppService.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jobs:
2323
self-contained: true
2424
ready-to-run: true
2525
application-insights-resource-id: "Azure resource ID of the corresponding AI resource"
26+
# Defaults to 'staging' if not set, used for adding git tags to the deployed commit.
27+
tag-prefix: staging
2628
secrets:
2729
AZURE_APP_SERVICE_DEPLOYMENT_SERVICE_PRINCIPAL_ID: ${{ secrets.AZURE_APP_SERVICE_DEPLOYMENT_SERVICE_PRINCIPAL_ID }}
2830
AZURE_APP_SERVICE_DEPLOYMENT_AZURE_TENANT_ID: ${{ secrets.AZURE_APP_SERVICE_DEPLOYMENT_AZURE_TENANT_ID }}

Docs/Workflows/AzureHosting/SwapAzureWebAppSlots.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,18 @@ jobs:
2020
source-slot-name: Staging
2121
destination-slot-name: Production
2222
application-insights-resource-id: "Azure resource ID of the corresponding AI resource"
23+
# Defaults to 'production' if not set, used for adding git tags to the deployed commit.
24+
tag-prefix: production
25+
# Defaults to 'staging' if not set, used looking up the currently deployed commit by tag.
26+
swap-prefix: staging
27+
# The repository that hosts the code that will be swapped. This is necessary for adding tags and releases.
28+
app-code-repository: Lombiq/Lombiq-GitHub-Actions
2329
secrets:
2430
AZURE_APP_SERVICE_SWAP_SERVICE_PRINCIPAL_ID: ${{ secrets.AZURE_APP_SERVICE_SWAP_SERVICE_PRINCIPAL_ID }}
2531
AZURE_APP_SERVICE_SWAP_AZURE_TENANT_ID: ${{ secrets.AZURE_APP_SERVICE_SWAP_AZURE_TENANT_ID }}
2632
AZURE_APP_SERVICE_SWAP_AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_APP_SERVICE_SWAP_AZURE_SUBSCRIPTION_ID }}
33+
# Needs to have access to create tags and releases in the code repository.
34+
CODE_REPOSITORY_WRITE_TOKEN: ${{ secrets.CODE_REPOSITORY_WRITE_TOKEN }}
2735
```
2836
2937
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.

0 commit comments

Comments
 (0)