-
Notifications
You must be signed in to change notification settings - Fork 158
871 lines (695 loc) · 43.5 KB
/
signoff-review.yml
File metadata and controls
871 lines (695 loc) · 43.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
name: User can sign off PR
permissions:
pull-requests: write
statuses: write
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened]
jobs:
build:
name: Run Script
if: github.repository_visibility == 'private'
runs-on: ubuntu-latest
steps:
- name: Script
shell: pwsh
env:
PayloadJson: ${{ toJSON(github) }}
AccessToken: ${{ secrets.GITHUB_TOKEN }}
# For testing set to a little used folder as the only target
MonitoredFolders: '[
"dms/",
"mariadb/",
"mysql/",
"postgresql/"
]'
run: |
# Print out current date and time
$currentDateTime = Get-Date
$currentDateTimeString = "{0:yyyy-MM-dd HH:mm:ss}" -f $currentDateTime
Write-Output "Run start: $currentDateTimeString"
# Get GitHub data and event
$GitHubData = $env:PayloadJson | ConvertFrom-Json -Depth 50
$GitRequestEvent = $GitHubData.event_name
if ($GitRequestEvent -eq "issue_comment")
{
$PrGitHubLink = $GitHubData.event.issue.html_url
}
$AccessToken = $env:AccessToken
$MonitoredFolders = $env:MonitoredFolders | ConvertFrom-Json
$DefaultBranch = $GitHubData.event.repository.default_branch
If ($GitRequestEvent -eq "issue_comment") {$GitHubState = $GitHubData.event.issue.state} ElseIf ($GitRequestEvent -eq "pull_request_target") {$GitHubState = $GitHubData.event.pull_request.state}
$GitHubAction = $GitHubData.event.action
$GitHubSender = $GitHubData.event.sender.login
$GitHubRepoName = $GitHubData.event.repository.name
$CommentUser = $GitHubData.event.comment.user.login
$PullRequestOpener = $GitHubData.event.pull_request.user.login
If ($GitRequestEvent -eq "issue_comment")
{
$PrIssueNumber = $GitHubData.event.issue.number
$PrUrl = $GitHubData.event.issue.pull_request.url
$IssueUrl = $GitHubData.event.issue.url
$CommentsUrl = $GitHubData.event.issue.comments_url
}
ElseIf ($GitRequestEvent -eq "pull_request_target")
{
$PrIssueNumber = $GitHubData.event.pull_request.number
$PrUrl = $GitHubData.event.pull_request.html_url
$IssueUrl = $GitHubData.event.pull_request.issue_url
$CommentsUrl = $GitHubData.event.pull_request.comments_url
}
If ($GitRequestEvent -eq "issue_comment") {$PrUrl = $GitHubData.event.issue.pull_request.url} ElseIf ($GitRequestEvent -eq "pull_request_target") {$PrUrl = $GitHubData.event.pull_request.url}
$UserPermissionUrl = $GitHubData.event.repository.collaborators_url.Replace("{/collaborator}", "/$CommentUser/permission" )
$UserPermissionPROpenerUrl = $GitHubData.event.repository.collaborators_url.Replace("{/collaborator}", "/$PullRequestOpener/permission" )
$RepoLabelUrl = $GitHubData.event.repository.labels_url
$GitHubHeaders = @{}
$GitHubHeaders.Add("Authorization","token $($AccessToken)")
$GitHubHeaders.Add("User-Agent", "OfficeDocs")
$StatusHelpUrl = "https://dev.azure.com/msft-skilling/Content/_wiki/wikis/Database%20Docs/2358/Partner-publishing-workflow-pilot"
$StatusCheckName = "PR signed off by content team"
$Status = @{}
$Status.Add("context", $StatusCheckName)
$Status.Add("target_url", $StatusHelpUrl)
$CatastrophicError = $False
$ContentApprovalMessage = "Approved by content team."
$ContentNeedsReviewMessage = "Needs review by content team when ready for sign off."
$ContentInReviewMessage = "PR is currently being reviewed by content team for approval."
$SignOffString = "#sign-off"
$ReviewingString = "#review-pr"
$ApprovedString = "#approve-pr"
$SignOffRegex = "\s*$SignOffString\s*"
$ReviewingRegex = "\s*$ReviewingString\s*"
$ApprovedRegex = "\s*$ApprovedString\s*"
$EscalationEmail = "[Data Docs Team](mailto:dbdocsprreviews@microsoft.com?subject=[PR%20REVIEW]%20Question%20on%20$GitHubRepoName%20PR%20$PrIssueNumber&body=$PrGitHubLink)"
$ContentLeadsUrl = "[the content lead in your area](https://dev.azure.com/msft-skilling/Content/_dashboards/dashboard/b25043af-cab3-4b2c-8aac-9e8da15a796b)"
$PROpenMessage = "Thank you for your contribution! All PRs will be reviewed and approved by the content team. They will remain in a pending state until approved even after they successfully build and stage. When you are ready for the content team to publish this PR, sign off as normal. If you have any questions, please reach out to $ContentLeadsUrl or email the $EscalationEmail."
$NeedsContentTeamReviewMessage = "The content team will review this pr and work with you to publish your changes. Thank you for working with us to improve content quality for our customers!`n`n**To expedite your review**, please reach out to $ContentLeadsUrl or email the $EscalationEmail."
$CurrentlyInReviewMessage = "This PR is currently assigned to the content team for review. It must be reviewed and approved by the content team before merging.`n`n**To expedite your review**, please reach out to $ContentLeadsUrl or email the $EscalationEmail."
$AssignedReviewerMessage = "Assigning the pull request to @!!USER!! for review. #assign: !!USER!!"
$CheckFailed = $False
$ContentReviewNeededLabelColor = "eeee88"
$ContentReviewNeededLabelDescription = "The pull request requires a review from the content team."
$ContentReviewNeededLabel = "needs-content-team-review"
$ContentReviewInProgressLabelColor = "33DDFF"
$ContentReviewInProgressLabelDescription = "A content team review has been initiated."
$ContentReviewInProgressLabel = "content-team-review"
$ContentApprovedLabelColor = "2A8000"
$ContentApprovedLabelDescription = "The content team approved the pull request."
$ContentApprovedLabel = "pr-approved"
Write-Host "Repo: $GitHubRepoName"
Write-Host "Sender: $GitHubSender"
Write-Host "Request event: $GitRequestEvent"
Write-Host "GitHub action: $GitHubAction"
Write-Host "GitHub state: $GitHubState"
Write-Host "Default branch: $DefaultBranch"
Write-Host "PR number: $PrIssueNumber"
Write-Host "Issue URL: $IssueUrl"
Write-Host "PR URL: $PrGitHubLink"
Write-Host "User Permission URL: $UserPermissionUrl"
Write-Host "User Permission URL (PR Opener): $UserPermissionPROpenerUrl"
Write-Host "Commenter user name: $CommentUser"
Write-Host "Pull request opener name: $PullRequestOpener"
# Make the job summary section show up so the job always looks consistent.
echo "" >> $env:GITHUB_STEP_SUMMARY
#####################
#####################
# Initialize-Labels
Function Initialize-Labels
{
Write-Host "Initialize labels needed by the workflow"
Initialize-Label -LabelName $ContentReviewNeededLabel -LabelColor $ContentReviewNeededLabelColor -LabelDescription $ContentReviewNeededLabelDescription
Initialize-Label -LabelName $ContentReviewInProgressLabel -LabelColor $ContentReviewInProgressLabelColor -LabelDescription $ContentReviewInProgressLabelDescription
Initialize-Label -LabelName $ContentApprovedLabel -LabelColor $ContentApprovedLabelColor -LabelDescription $ContentApprovedLabelDescription
}
#####################
#####################
# Initialize-Label
Function Initialize-Label {
[CmdletBinding()]
param(
$LabelName,
$LabelColor,
$LabelDescription
)
# Check if label exists on the *REPO*.
$LabelExists = Test-RepoLabel -RepoUri $RepoLabelUrl -Name $LabelName
If (!$LabelExists)
{
# Create label on the *REPO* if it doesn't exist.
New-RepoLabel -RepoUri $RepoLabelUrl -Name $LabelName -Color $LabelColor -Description $LabelDescription
}
}
#####################
#####################
# Test-RepoLabel
Function Test-RepoLabel {
[CmdletBinding()]
param(
$Name,
$RepoUri
)
# Replace placeholder text in the URL retrieved from the GitHub API with the name of the label we're looking for
$LabelUri = $RepoUri.Replace("{/name}","/$Name")
# Check to see if the label we want exists in the repo
Try {
Write-Host "Checking to see if label $Name exists in repo URL $LabelUri."
$LabelResults = Invoke-WebRequest -UseBasicParsing -Uri $LabelUri -Headers $GitHubHeaders -ErrorAction Stop
$LabelFound = $True
} Catch {
# OK if label doesn't exist. Just means we need to create it.
$LabelFound = $False
}
# Return boolean to calling statement
$LabelFound
}
#####################
#####################
# New-RepoLabel
Function New-RepoLabel {
[CmdletBinding()]
param(
$Name,
$Color,
$Description,
$RepoUri
)
# Remove placeholder text from repo URL
$RepoUri = $RepoUri.Replace("{/name}","")
$Result = $Null
# Construct the JSON statement that will be sent to GitHub as the body of the web request. Include the name of the label, its color, and description.
# Convert hash table to JSON
$Body = @{}
$Body.Add("name", $Name)
$Body.Add("color", $Color)
$Body.Add("description", $description)
$Body = $Body | ConvertTo-Json
# Try to submit the request to GitHub API to create the label
Try {
Write-Host "Creating label $Name in repo $RepoUri."
$Result = Invoke-RestMethod -Uri $RepoUri -Headers $GitHubHeaders -Body $Body -Method POST
} Catch {
Write-Error "ERROR: Failed to create new label $Name on repo $RepoUri. Error: $($Error[0].Exception.Message)."
}
}
#####################
#####################
# Test-PrLabel
Function Test-PrLabel {
[CmdletBinding()]
param(
$LabelArray,
$IssueUrl
)
# Replace placeholder text in the URL retrieved from the GitHub API with the name of the label we're looking for
$IssueLabelUrl = "$IssueUrl/labels"
$LabelHashTable = @{}
$LabelResults = $Null
# Get list of labels on issue/PR
Try
{
$LabelResults = Invoke-RestMethod -Uri $IssueLabelUrl -Headers $GitHubHeaders -ErrorAction Stop
}
Catch
{
Write-Error "ERROR: Failed to get list of labels on $IssueLabelUrl. Error: $($Error[0].Exception.Message)."
}
ForEach ($Label in $LabelArray) {
If ($LabelResults -ne $Null) {
If ($LabelResults.name.Contains($Label)) {
$LabelHashTable.Add($Label, $True)
} Else {
$LabelHashTable.Add($Label, $False)
}
} Else {
$LabelHashTable.Add($Label, $False)
}
}
# Return array of labels on Issue/PR
Return $LabelHashTable
}
#####################
#####################
# Set-PrLabel
Function Set-PrLabel {
param(
$IssueUrl,
$LabelName
)
# Check to see if the label exists on the *PR*.
$LabelResultsArray = Test-PrLabel -LabelArray $LabelName -IssueUrl $IssueUrl
# Only add the label if it doesn't already exist on the *PR*
If (!$LabelResultsArray.$LabelName)
{
# Construct label URL based on issue or pull request URL
$IssueLabelUrl = "$IssueUrl/labels"
# Construct JSON statement that will be sent to GitHub as the body of the web request. Includes only the label name. GitHub expects an array even thought it's a single value
# Convert array to JSON
$Body = @()
$Body += $LabelName
$Body = ConvertTo-Json -InputObject $Body
# Try to submit the request to GitHub API to apply they label to the issue or pull request
Try
{
Write-Host "Setting label $LabelName on URL $IssueLabelUrl."
$Result = Invoke-RestMethod -Uri $IssueLabelUrl -Body $Body -Headers $GitHubHeaders -Method POST
}
Catch
{
Write-Error "ERROR: Failed to set label $LabelName on URL $IssueLabelUrl. Error: $($Error[0].Exception.Message)."
}
}
Else
{
Write-Host "Label $LabelName already exists on PR"
}
}
#####################
#####################
Function Remove-Label
{
param(
$IssueUrl,
$LabelName
)
Write-Host "Remove $LabelName"
# Check to see if the approved label exists on the *PR* and remove it
$LabelResultsArray = Test-PrLabel -LabelArray $LabelName -IssueUrl $IssueUrl
if ($LabelResultsArray.$LabelName)
{
Write-Host "Removing "$LabelName" label from PR."
$LabelUrlName = $LabelName.Replace(" ", "%20")
$LabelUrl = "$IssueUrl/labels/$LabelUrlName"
Write-Host "$LabelUrl"
$Result = Invoke-WebRequest -UseBasicParsing -Uri $LabelUrl -Headers $GitHubHeaders -Method Delete -ErrorAction Stop
Write-Host "Successfully removed $LabelName label."
}
}
#####################
#####################
# Test-MonitoredFolders
Function Test-MonitoredFolders {
[CmdletBinding()]
param(
$PrFileList,
$MonitoredFolderList
)
$MonitoredFolderFound = $False
ForEach ($MonitoredFolder in $MonitoredFolderList) {
write-host $MonitoredFolder
$MonitoredFolderFound = [bool]($PrFileList | Where {$_.filename -like "*$MonitoredFolder*"})
If ($MonitoredFolderFound) {
Break
}
}
Return $MonitoredFolderFound
}
#####################
#####################
# Set-PrComment
Function Set-PrComment
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$True)]
$Message
)
Write-Host "Adding comment in pr..."
$BodyHash = @{}
$BodyHash.body = $Message
$BodyJson = $BodyHash | ConvertTo-Json
Try
{
$Result = Invoke-WebRequest -UseBasicParsing -Uri $CommentsUrl -Body $BodyJson -Headers $GitHubHeaders -Method POST -ErrorAction Stop
$PostCommentSuccess = $True
}
Catch
{
$PostCommentSuccess = $False
Write-Host "ERROR: Failed to submit message to PR conversation. Error: $($error[0].Exception.Message)."
}
Return $PostCommentSuccess
}
#####################
#####################
# Add-PrAssignee
Function Add-PrAssignee
{
param
(
$PrData,
$GitHubAssignee
)
Try
{
$Assignees = $PrData.assignees
$CurrentAssignees = $PrData.assignees | ForEach-Object { $_.login }
Write-Host "Current assignees: $CurrentAssignees"
$AlreadyAdded = $False
# Loop through each of the eligible assignees found in the PR.
ForEach ($User in $Assignees)
{
If ($User.login -eq $GitHubAssignee)
{
Write-Host "Found matching user already assigned to PR"
$AlreadyAdded = $True
}
}
if (-not $AlreadyAdded)
{
# User is not already assigned to the PR. Assign them using the #assign comment
# that PR Merger will read and act on
Write-Host "Adding user $GitHubAssignee to $IssueUrl"
$AssignedReviewerMessage = $AssignedReviewerMessage -replace "!!USER!!", $GitHubAssignee
Set-PrComment -Message $AssignedReviewerMessage
}
else
{
Write-Host "User $GitHubAssignee already assigned to PR."
}
}
Catch
{
Write-Host "Error assigning user to PR! Error: $($error[0].Exception.Message)."
}
}
#####################
#####################
# Workflow #
#####################
#####################
Try
{
# Get PR data so we can get the base branch of the PR. Doing this here so we don't need to do unnecessary calls if other criteria fail.
$PrData = Invoke-RestMethod -Method GET -Headers $GitHubHeaders -Uri $PrUrl
$TargetBranch = $PrData.base.ref
$StatusUrl = $PrData.statuses_url
$PrHtmlUrl = $PrData.html_url
Write-Host "PR status url: $StatusUrl"
# Only run checks if target is default branch. Otherwise let it pass.
#
# To test against a target branch other than the default branch (main), set "$DefaultBranch" to the name of a test branch
# and place this workflow in that branch's .github/workflows folder. Then create another test branch off the first, and create
# a PR to the target test branch. Remember to comment out or remove the $DefaultBranch assignment after testing. :)
#
# $DefaultBranch = "do-not-merge-signoff-workflow"
#
# For now using folder check rather than the branch check for testing
# Check to see if this is a branch that is being monitored:
Write-Host "Target branch: $TargetBranch"
$TargetingMonitoredBranch = ($TargetBranch -eq $DefaultBranch)
Write-Host "Branch is targeted for monitoring: $TargetingMonitoredBranch"
# Check to see if this is an actionable comment
Write-Host "GitHub Request Event: $GitRequestEvent"
Write-Host "GitHub Action: $GitHubAction"
$SignOffFound = $false
$ReviewingFound = $false
$ApprovedFound = $false
$ActionableComment = $false
$PROpenEvent = $false
$EventIsCommentCreated = (($GitRequestEvent -eq "issue_comment") -and (($GitHubAction -eq "created")))
If ($EventIsCommentCreated)
{
Write-Host "Comment added on PR."
# Get the contents of the comment that was added to the PR
$CommentBody = $GitHubData.event.comment.body
Write-Host "Comment:"
Write-Host "--------------------------"
Write-Host "$CommentBody"
Write-Host "--------------------------"
# Check to see if comment includes #sign-off, #reviewing, or #approved
$SignOffFound = $CommentBody -match $SignOffRegex
$ReviewingFound = $CommentBody -match $ReviewingRegex
$ApprovedFound = $CommentBody -match $ApprovedRegex
$ActionableComment = ($SignOffFound -or $ApprovedFound -or $ReviewingFound)
}
ElseIf (($GitRequestEvent -eq "pull_request_target") -and (($GitHubAction -eq "opened") -or ($GitHubAction -eq "reopened") -or ($GitHubAction -eq "synchronize")))
{
Write-Host "Non-comment GitHub Event"
Write-Host "Pull request action $GitHubAction. Setting sign off check to pending."
$PROpenEvent = $true
$Status.state = "pending"
$Status.description = "Waiting for sign off."
}
else
{
Write-Host "GitHub event not a PR comment"
}
if ($PROpenEvent)
{
# This runs when the PR is opened and does a check on the PR opener, not the commenter
# Get permission level of user who created the PR. Need to use .role_name instead of .permission because .permission provides only legacy values.
# .role_name provides legacy plus triage, maintain, and custom roles like write-elevated.
$UserPermission = $(Invoke-RestMethod -Method GET -Headers $GitHubHeaders -Uri $UserPermissionPROpenerUrl).role_name
Write-Host "User $PullRequestOpener permission level: $UserPermission."
# If user has write or above, allow check to pass. If not, provide a welcome message
If (($UserPermission -like "write*") -or ($UserPermission -eq "maintain") -or ($UserPermission -eq "admin") -or ($UserPermission -eq "triage"))
{
Write-Host "Approving as pull request creator $PullRequestOpener has permission."
Initialize-Labels
Set-PrLabel -IssueUrl $IssueUrl -LabelName $ContentApprovedLabel
$Status.state = "success"
$Status.description = "Pull request opener $PullRequestOpener is pre-approved."
}
Else
{
# Provide a welcome message
Write-Host "User creating PR is not pre-approved. Adding welcome message."
Set-PrComment $PROpenMessage
}
$ActionableComment = $True
}
else
{
# This was a comment on the PR so look for action tags
Write-Host "Regex result found $SignOffString : $SignOffFound."
Write-Host "Regex result found $ReviewingString : $ReviewingFound."
Write-Host "Regex result found $ApprovedString : $ApprovedFound."
# Check to see if it is a monitored folder
# Get the list of files on the PR.
Write-Host "Checking for monitored folders in PR"
$PrFiles = Invoke-RestMethod -Method GET -Headers $GitHubHeaders -Uri "$PrUrl/files"
# Check to see if any files in the PR exist in one or more monitored branches. Only a single
# file needs to be in a monitored branch for the workflow to block the PR.
$MonitoredFoldersFound = Test-MonitoredFolders -PrFileList $PrFiles -MonitoredFolderList $MonitoredFolders
# Check to see what labels are already on the PR
Initialize-Labels
$LabelReviewNeededArray = Test-PrLabel -LabelArray $ContentReviewNeededLabel -IssueUrl $IssueUrl
$LabelReviewInProgressArray = Test-PrLabel -LabelArray $ContentReviewInProgressLabel -IssueUrl $IssueUrl
$LabelApprovedArray = Test-PrLabel -LabelArray $ContentApprovedLabel -IssueUrl $IssueUrl
$ReviewNeededLabelSet = $LabelReviewNeededArray.$ContentReviewNeededLabel
$ReviewInProgressLabelSet = $LabelReviewInProgressArray.$ContentReviewInProgressLabel
$ApprovedLabelSet = $LabelApprovedArray.$ContentApprovedLabel
Write-Host "Monitored folders found: $MonitoredFoldersFound"
# Workflow processing:
If (-not $TargetingMonitoredBranch)
{
Write-Host "Approving as $TargetBranch branch is not being monitored."
$ActionableComment = $True
Set-PrLabel -IssueUrl $IssueUrl -LabelName $ContentApprovedLabel
$Status.state = "success"
$Status.description = "Target branch not $DefaultBranch, so approving by default."
}
ElseIf (-not $MonitoredFoldersFound)
{
Write-Host "Approving as no monitored folders found."
$ActionableComment = $True
Set-PrLabel -IssueUrl $IssueUrl -LabelName $ContentApprovedLabel
$Status.state = "success"
$Status.description = "No monitored folders. Allowing sign off."
}
ElseIf (-not $ActionableComment)
{
Write-Host "Comment not sign off, review, or approval."
# Check to see if the content team has approved this yet
# If so, always return success. If not, always keep at pending until approved.
if ($ApprovedLabelSet)
{
$ActionableComment = $True
Write-Host "Content team already approved. Returning success."
$Status.state = "success"
$Status.description = $ContentApprovalMessage
}
else
{
# The comment was not one to take action on
# In the past, we returned pending, but below we are just not returning
# any status from the workflow so we don't change anything
# This status is left in case we want to go back to pending in this case
Write-Host "Folders monitored and no content team approval yet. Returning pending."
$Status.state = "pending"
$Status.description = $ContentNeedsReviewMessage
}
}
Else
{
# The branch is targeted, the folder is monitored, and the comment contains an action
Initialize-Labels
Write-Host "String $SignOffString or $ApprovedString or $ReviewingString found on PR/Issue #$PrIssueNumber."
# Get permission level of user who created the comment. Need to use .role_name instead of .permission because .permission provides only legacy values.
# .role_name provides legacy plus triage, maintain, and custom roles like write-elevated.
$UserPermission = $(Invoke-RestMethod -Method GET -Headers $GitHubHeaders -Uri $UserPermissionUrl).role_name
Write-Host "User $CommentUser permission level: $UserPermission."
# If user has write or above, allow check to pass. If not, add content team review label.
If (($UserPermission -like "write*") -or ($UserPermission -eq "maintain") -or ($UserPermission -eq "admin") -or ($UserPermission -eq "triage"))
{
Write-Host "User $CommentUser has the permission level: $UserPermission. Pass check."
If ($SignOffFound -or $ApprovedFound)
{
# Set the properties on the $Status object to allow the check to pass. $Status will be sent to GitHub at the end of the workflow.
$Status.state = "success"
$Status.description = $ContentApprovalMessage
Write-Host "Adding $ContentApprovedLabel label to the PR"
Set-PrLabel -IssueUrl $IssueUrl -LabelName $ContentApprovedLabel
If ($ApprovedFound)
{
# If explicitly approved, set the content team reviewed label as well for tracking
Set-PrLabel -IssueUrl $IssueUrl -LabelName $ContentReviewInProgressLabel
}
Write-Host "Removing other labels"
# Don't remove the in progress label as we can see this to see what we reviewed
# Remove-Label -LabelName $ContentReviewInProgressLabel -IssueUrl $IssueUrl
Remove-Label -LabelName $ContentReviewNeededLabel -IssueUrl $IssueUrl
# Also assign the user to the PR. This way the person who gave approval or signed off
# can shepherd the PR through the final stages of publishing.
Write-Host "Assigning $CommentUser to the PR"
Add-PrAssignee -PrData $PrData -GitHubAssignee $CommentUser
}
ElseIf ($ReviewingFound)
{
Write-Host "Adding $ContentReviewInProgressLabel label to the PR"
# Status is pending while reviewing
$Status.state = "pending"
$Status.description = $ContentInReviewMessage
Set-PrLabel -IssueUrl $IssueUrl -LabelName $ContentReviewInProgressLabel
# Content team is explicitly setting this back to review state, so remove any approved tags and add review tag.
Write-Host "Removing other labels"
Remove-Label -LabelName $ContentApprovedLabel -IssueUrl $IssueUrl
Remove-Label -LabelName $ContentReviewNeededLabel -IssueUrl $IssueUrl
# Also assign the user to the PR. This way the person who added the reviewing label
# can shepherd the PR through the final stages of publishing.
Write-Host "Assigning $CommentUser to the PR"
Add-PrAssignee -PrData $PrData -GitHubAssignee $CommentUser
}
}
Else
{
# If the content team has already approved, report success.
# Technically, this means that once approved they could sneak other changes in, but maybe that is ok as we would see.
If ($ApprovedLabelSet)
{
Write-Host "Content team already approved. Returning success."
$Status.state = "success"
$Status.description = $ContentApprovalMessage
}
Else
{
# Check failed. Add label.
Write-Host "One or more files in monitored folders and user $CommentUser has the permission level: $UserPermission. Fail check."
If ($ReviewInProgressLabelSet)
{
Write-Host "$ContentReviewInProgressLabel already set, so not changing label or status."
# Already in review so just keep the same status and message
$Status.state = "pending"
$Status.description = $ContentInReviewMessage
Write-Host "User signed off without permissions but it is already in review with content team"
Set-PrComment $CurrentlyInReviewMessage
}
Else
{
Write-Host "Adding the $ContentReviewNeededLabel label."
Set-PrLabel -IssueUrl $IssueUrl -LabelName $ContentReviewNeededLabel
# Even though the check failed, leave the status as pending.
# It is always pending until the content team has approved.
$Status.state = "pending"
$Status.description = $ContentNeedsReviewMessage
Set-PrComment $NeedsContentTeamReviewMessage
}
$CheckFailed = $True
}
}
}
if ($Status.state -ne "success")
{
# If the state is anything other than success, attempt to remove the ready-to-merge label
# and add the do-not-merge label
Write-Host "Removing ready-to-merge label"
Remove-Label -LabelName "ready-to-merge" -IssueUrl $IssueUrl
Write-Host "Adding the do-not-merge label"
Set-PrLabel -LabelName "do-not-merge" -IssueUrl $IssueUrl
}
}
}
Catch
{
# Capture and display detailed exception information
Write-Host "An error occurred: $($_.Exception.Message)"
Write-Host "Exception type: $($_.Exception.GetType().FullName)"
Write-Host "Line number: $($_.InvocationInfo.ScriptLineNumber)"
Write-Host "Position: $($_.InvocationInfo.OffsetInLine)"
Write-Host "Script name: $($_.InvocationInfo.ScriptName)"
Write-Host "Error line: $($_.InvocationInfo.Line)"
# Read and display the line of code that caused the error
$scriptPath = $($_.InvocationInfo.ScriptName)
if ($scriptPath) {
$lines = Get-Content -Path $scriptPath
$errorLine = $_.InvocationInfo.ScriptLineNumber
Write-Host "Code at error line $errorLine : $($lines[$errorLine - 1])"
}
# For catastrophic failure, make sure to not block PRs
$Status.state = "success"
$Status.description = "Unexpected error in workflow. Allowing sign off."
$CatastrophicError = $True
}
# Get ready to send $Status to GitHub. First need to convert the $Status object to JSON. Then set a couple variables to prepare for an attempt loop
# to deal with transient communication failures that may prevent the workflow from setting the status.
$StatusJson = $Status | ConvertTo-Json
$SuccessfulPost = $False
$RetryCount = 0
Write-Host "Reporting Status:"
Write-Host "$StatusJson"
# Loop to try set the status on the PR. Keep trying until either the status is successfully posted or we run out of attempts.
# In the unlikely event of a water landing or failure of all six attempts, the status will not set and the user will need to
# submit a new sign off request.
if ($ActionableComment)
{
# Only post a status back if an actionable comment was provided.
# Otherwise leave as expected with no result.
# This keeps the previous status unless explicitly changed.
Do
{
Try
{
# Send POST request to GitHub
Invoke-RestMethod -Headers $GitHubHeaders -Uri $StatusUrl -Method POST -Body $StatusJson -ErrorAction Stop
$SuccessfulPost = $True
}
Catch
{
# If the request fails for any reason, retry it after a delay, up to six times.
$RetryCount++
Start-Sleep 1
}
} Until (($SuccessfulPost) -or ($RetryCount -gt 5))
}
# Print out current date and time
$currentDateTime = Get-Date
$currentDateTimeString = "{0:yyyy-MM-dd HH:mm:ss}" -f $currentDateTime
Write-Output "Run end: $currentDateTimeString"
If ($CheckFailed)
{
# Populates the job summary if a user doesn't have permissions to sign off.
echo "# Pull request validation error" >> $env:GITHUB_STEP_SUMMARY
echo "" >> $env:GITHUB_STEP_SUMMARY
echo "The user $CommentUser with permission level $UserPermission tried to sign off PR: $PrHtmlUrl but doesn't have the necessary permissions to do so. Please contact the content team to request sign off." >> $env:GITHUB_STEP_SUMMARY
}
ElseIf ($CatastrophicError)
{
# Makes sure the workflow looks failed so we can investigate the bug
echo "# Pull request validation error" >> $env:GITHUB_STEP_SUMMARY
echo "" >> $env:GITHUB_STEP_SUMMARY
echo "The user $CommentUser with permission level $UserPermission tried to sign off PR: $PrHtmlUrl. There was an unexpected error in the workflow, so sign-off was allowed to prevent blocking PRs. Please contact the developer of the workflow to investigate." >> $env:GITHUB_STEP_SUMMARY
# Force the workflow to fail so the validation failure can be tracked in Actions. In this workflow, this has no effect other than logging the failure.
Throw "User $CommentUser signed off but an unexpected error occurred. Please contact contact team to investigate the workflow. Sign off allowed to prevent blocking PRs."
}