diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index e4f02108a3..d136649c08 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -1396,19 +1396,32 @@ jobs: name: CI Completion Gate needs: [ pages-build, docker-build, build-deb, build-msi, validate-openapi-spec, upload-code-coverage, check-winget-pr-template, code-scanning ] runs-on: ubuntu-latest - permissions: - checks: write - contents: read if: (!(cancelled() || failure()) && needs.pages-build.result == 'success' && needs.docker-build.result == 'success' && needs.build-deb.result == 'success' && needs.build-msi.result == 'success' && needs.validate-openapi-spec.result == 'success' && needs.upload-code-coverage.result == 'success' && needs.check-winget-pr-template.result == 'success' && needs.code-scanning.result == 'success') steps: - - name: Create Completion Check - uses: LouisBrunner/checks-action@6b626ffbad7cc56fd58627f774b9067e6118af23 - with: - token: ${{ secrets.GITHUB_TOKEN }} - name: CI Completion - conclusion: success - output: | - {"summary":"The CI Pipeline completed successfully"} + - name: Setup dotnet + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '${{ env.TGS_DOTNET_VERSION }}.0.x' + dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }} + + - name: Checkout (Branch) + uses: actions/checkout@v4 + if: github.event_name == 'push' || github.event_name == 'schedule' + + - name: Checkout (PR Merge) + uses: actions/checkout@v4 + if: github.event_name != 'push' && github.event_name != 'schedule' + with: + ref: "refs/pull/${{ github.event.number }}/merge" + + - name: Restore + run: dotnet restore + + - name: Build ReleaseNotes + run: dotnet build -c Release -p:TGS_HOST_NO_WEBPANEL=true tools/Tgstation.Server.ReleaseNotes/Tgstation.Server.ReleaseNotes.csproj + + - name: Run ReleaseNotes Create CI Completion Check + run: dotnet run -c Release --no-build --project tools/Tgstation.Server.ReleaseNotes --ci-completion-check ${{ github.sha }} ${{ secrets.TGS_CI_GITHUB_APP_TOKEN_BASE64 }} deployment-gate: name: Deployment Start Gate diff --git a/tools/Tgstation.Server.ReleaseNotes/Program.cs b/tools/Tgstation.Server.ReleaseNotes/Program.cs index 6130bbc17a..d5ac69e564 100644 --- a/tools/Tgstation.Server.ReleaseNotes/Program.cs +++ b/tools/Tgstation.Server.ReleaseNotes/Program.cs @@ -1,20 +1,25 @@ // This program is minimal effort and should be sent to remedial school using System; +using System.Buffers.Text; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.IdentityModel.Tokens.Jwt; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Security; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Xml.Linq; +using Microsoft.IdentityModel.Tokens; + using Newtonsoft.Json; using Octokit; @@ -32,8 +37,11 @@ namespace Tgstation.Server.ReleaseNotes static class Program { const string OutputPath = "release_notes.md"; + + // some stuff that should be abstracted for different repos const string RepoOwner = "tgstation"; const string RepoName = "tgstation-server"; + const int AppId = 847638; /// /// The entrypoint for the @@ -52,13 +60,15 @@ static async Task Main(string[] args) var shaCheck = versionString.Equals("--winget-template-check", StringComparison.OrdinalIgnoreCase); var fullNotes = versionString.Equals("--generate-full-notes", StringComparison.OrdinalIgnoreCase); var nuget = versionString.Equals("--nuget", StringComparison.OrdinalIgnoreCase); + var ciCompletionCheck = versionString.Equals("--ci-completion-check", StringComparison.OrdinalIgnoreCase); if ((!Version.TryParse(versionString, out var version) || version.Revision != -1) && !ensureRelease && !linkWinget && !shaCheck && !fullNotes - && !nuget) + && !nuget + && !ciCompletionCheck) { Console.WriteLine("Invalid version: " + versionString); return 2; @@ -129,6 +139,17 @@ static async Task Main(string[] args) return await Winget(client, actionsUrl, null); } + if (ciCompletionCheck) + { + if (args.Length < 3) + { + Console.WriteLine("Missing SHA or PEM Base64 for creating check run!"); + return 4543; + } + + return await CICompletionCheck(client, args[1], args[2]); + } + if (shaCheck) { if(args.Length < 2) @@ -1583,6 +1604,47 @@ static async Task GenDebianChangelog(IGitHubClient client, Version version, return 0; } + static async ValueTask CICompletionCheck(GitHubClient gitHubClient, string currentSha, string pemBase64) + { + var pemBytes = Convert.FromBase64String(pemBase64); + var pem = Encoding.UTF8.GetString(pemBytes); + + var rsa = RSA.Create(); + rsa.ImportFromPem(pem); + + var signingCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256); + var jwtSecurityTokenHandler = new JwtSecurityTokenHandler { SetDefaultTimesOnTokenCreation = false }; + + var now = DateTime.UtcNow; + + var jwt = jwtSecurityTokenHandler.CreateToken(new SecurityTokenDescriptor + { + Issuer = AppId.ToString(), + Expires = now.AddMinutes(10), + IssuedAt = now, + SigningCredentials = signingCredentials + }); + + var jwtStr = jwtSecurityTokenHandler.WriteToken(jwt); + + gitHubClient.Credentials = new Credentials(jwtStr, AuthenticationType.Bearer); + + var installation = await gitHubClient.GitHubApps.GetRepositoryInstallationForCurrent(RepoOwner, RepoName); + var installToken = await gitHubClient.GitHubApps.CreateInstallationToken(installation.Id); + + gitHubClient.Credentials = new Credentials(installToken.Token); + + await gitHubClient.Check.Run.Create(RepoOwner, RepoName, new NewCheckRun("CI Completion", currentSha) + { + CompletedAt = now, + Conclusion = CheckConclusion.Success, + Output = new NewCheckRunOutput("CI Completion", "The CI Pipeline completed successfully"), + Status = CheckStatus.Completed, + }); + + return 0; + } + static void DebugAssert(bool condition, string message = null) { // This exists because one of the fucking asserts evaluates an enumerable or something and it was getting optimized out in release