mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-01-01 19:08:19 -05:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
381 lines
10 KiB
C#
381 lines
10 KiB
C#
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace Facepunch;
|
|
|
|
internal static class GitHub
|
|
{
|
|
private static readonly HttpClient httpClient = new();
|
|
private static readonly Dictionary<string, long> checkRunIds = [];
|
|
|
|
public record struct CheckRunParams(
|
|
string Name,
|
|
string Status,
|
|
string Conclusion = null,
|
|
string Title = null,
|
|
string Summary = null,
|
|
string Details = null
|
|
);
|
|
|
|
/// <summary>
|
|
/// Post a check run status to GitHub
|
|
/// </summary>
|
|
public static bool PostCheckRun( CheckRunParams parameters )
|
|
{
|
|
if ( !Utility.IsCi() )
|
|
return true;
|
|
|
|
try
|
|
{
|
|
return PostCheckRunAsync( parameters ).GetAwaiter().GetResult();
|
|
}
|
|
catch ( Exception ex )
|
|
{
|
|
Console.WriteLine( $"Error posting check run: {ex.Message}" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the check run ID for a step name if available
|
|
/// </summary>
|
|
public static bool TryGetCheckRunId( string stepName, out long checkRunId )
|
|
{
|
|
return checkRunIds.TryGetValue( stepName, out checkRunId );
|
|
}
|
|
|
|
private static async Task<bool> PostCheckRunAsync( CheckRunParams parameters )
|
|
{
|
|
// Validate required environment variables
|
|
if ( !TryGetTokenAndRepositoryFromEnv( out var token, out var repository ) )
|
|
return false;
|
|
|
|
// Get the SHA
|
|
string sha = GetCommitSha();
|
|
if ( string.IsNullOrEmpty( sha ) )
|
|
return false;
|
|
|
|
// Get timestamps in ISO 8601 format
|
|
string currentTime = DateTime.UtcNow.ToString( "yyyy-MM-ddTHH:mm:ssZ" );
|
|
|
|
// Set headers for all requests
|
|
SetupHttpClient( token );
|
|
|
|
// If we have an ID for this check run, update it instead of creating a new one
|
|
if ( checkRunIds.TryGetValue( parameters.Name, out long checkRunId ) )
|
|
{
|
|
if ( await UpdateCheckRun( repository, checkRunId, parameters, currentTime ) )
|
|
{
|
|
return true;
|
|
}
|
|
// If update failed, we'll fall through to create a new check run
|
|
}
|
|
|
|
// Create a new check run
|
|
return await CreateCheckRun( repository, sha, parameters, currentTime );
|
|
}
|
|
|
|
private static async Task<bool> UpdateCheckRun(
|
|
string repository, long checkRunId, CheckRunParams parameters, string currentTime )
|
|
{
|
|
// Build update payload - slightly different from create payload (no head_sha)
|
|
object updatePayload;
|
|
var outputObject = new
|
|
{
|
|
title = parameters.Title ?? parameters.Name,
|
|
summary = parameters.Summary ?? string.Empty,
|
|
text = parameters.Details ?? string.Empty
|
|
};
|
|
|
|
if ( parameters.Status == "completed" )
|
|
{
|
|
updatePayload = new
|
|
{
|
|
status = parameters.Status,
|
|
conclusion = parameters.Conclusion ?? "neutral",
|
|
completed_at = currentTime,
|
|
output = outputObject
|
|
};
|
|
}
|
|
else
|
|
{
|
|
updatePayload = new
|
|
{
|
|
status = parameters.Status,
|
|
output = outputObject
|
|
};
|
|
}
|
|
|
|
var jsonContent = JsonSerializer.Serialize( updatePayload );
|
|
var content = new StringContent( jsonContent, Encoding.UTF8, "application/json" );
|
|
|
|
var updateUrl = $"https://api.github.com/repos/{repository}/check-runs/{checkRunId}";
|
|
|
|
// Use PATCH to update the existing check run
|
|
var response = await httpClient.PatchAsync( updateUrl, content );
|
|
|
|
if ( !response.IsSuccessStatusCode )
|
|
{
|
|
var responseContent = await response.Content.ReadAsStringAsync();
|
|
Console.WriteLine( $"GitHub API error updating check run: {response.StatusCode} - {responseContent}" );
|
|
|
|
// If the check run no longer exists (404), remove it from our dictionary
|
|
// and create a new one instead
|
|
if ( response.StatusCode == System.Net.HttpStatusCode.NotFound )
|
|
{
|
|
checkRunIds.Remove( parameters.Name );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static async Task<bool> CreateCheckRun(
|
|
string repository, string sha, CheckRunParams parameters, string currentTime )
|
|
{
|
|
object updatePayload;
|
|
var outputObject = new
|
|
{
|
|
title = parameters.Title ?? parameters.Name,
|
|
summary = parameters.Summary ?? string.Empty,
|
|
text = parameters.Details ?? string.Empty
|
|
};
|
|
|
|
if ( parameters.Status == "completed" )
|
|
{
|
|
updatePayload = new
|
|
{
|
|
name = parameters.Name,
|
|
head_sha = sha,
|
|
status = parameters.Status,
|
|
conclusion = parameters.Conclusion ?? "neutral",
|
|
started_at = currentTime,
|
|
completed_at = currentTime,
|
|
output = outputObject
|
|
};
|
|
}
|
|
else
|
|
{
|
|
updatePayload = new
|
|
{
|
|
name = parameters.Name,
|
|
head_sha = sha,
|
|
status = parameters.Status,
|
|
started_at = currentTime,
|
|
output = outputObject
|
|
};
|
|
}
|
|
|
|
var jsonContent = JsonSerializer.Serialize( updatePayload );
|
|
var content = new StringContent( jsonContent, Encoding.UTF8, "application/json" );
|
|
|
|
var url = $"https://api.github.com/repos/{repository}/check-runs";
|
|
|
|
// Send request to create a new check run
|
|
var response = await httpClient.PostAsync( url, content );
|
|
|
|
if ( !response.IsSuccessStatusCode )
|
|
{
|
|
var errorResponseContent = await response.Content.ReadAsStringAsync();
|
|
Console.WriteLine( $"GitHub API error creating check run: {response.StatusCode} - {errorResponseContent}" );
|
|
return false;
|
|
}
|
|
|
|
var responseContent = await response.Content.ReadAsStringAsync();
|
|
|
|
// Simple parsing of the response to get the ID
|
|
using var jsonDoc = JsonDocument.Parse( responseContent );
|
|
if ( jsonDoc.RootElement.TryGetProperty( "id", out var idElement ) )
|
|
{
|
|
long id = idElement.GetInt64();
|
|
checkRunIds[parameters.Name] = id;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a URL to the current workflow's logs
|
|
/// </summary>
|
|
public static string GetWorkflowLogsUrl()
|
|
{
|
|
return GetWorkflowLogsUrlAsync().GetAwaiter().GetResult();
|
|
}
|
|
|
|
private static async Task<string> GetWorkflowLogsUrlAsync()
|
|
{
|
|
// Get required environment variables
|
|
if ( !TryGetTokenAndRepositoryFromEnv( out var token, out var repository ) )
|
|
return string.Empty;
|
|
|
|
SetupHttpClient( token );
|
|
|
|
var runId = Environment.GetEnvironmentVariable( "GITHUB_RUN_ID" );
|
|
if ( string.IsNullOrEmpty( runId ) )
|
|
{
|
|
Console.WriteLine( "GITHUB_RUN_ID not set" );
|
|
return string.Empty;
|
|
}
|
|
|
|
long jobId = await FindJobId( repository, runId );
|
|
|
|
// Construct the logs URL
|
|
if ( jobId != -1 )
|
|
{
|
|
return $"https://github.com/{repository}/actions/runs/{runId}/job/{jobId}";
|
|
}
|
|
|
|
return $"https://github.com/{repository}/actions/runs/{runId}";
|
|
}
|
|
|
|
private static async Task<long> FindJobId( string repository, string runId )
|
|
{
|
|
long jobId = -1;
|
|
try
|
|
{
|
|
// Query the API to get jobs for this run
|
|
var jobsUrl = $"https://api.github.com/repos/{repository}/actions/runs/{runId}/jobs";
|
|
var response = await httpClient.GetAsync( jobsUrl );
|
|
|
|
if ( !response.IsSuccessStatusCode )
|
|
{
|
|
var errorContent = await response.Content.ReadAsStringAsync();
|
|
Console.WriteLine( $"GitHub API error: {response.StatusCode} - {errorContent}" );
|
|
return jobId;
|
|
}
|
|
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
using var doc = JsonDocument.Parse( content );
|
|
|
|
// Get the current job name
|
|
var jobName = Environment.GetEnvironmentVariable( "GITHUB_JOB" );
|
|
|
|
// Make sure the jobs property exists
|
|
if ( !doc.RootElement.TryGetProperty( "jobs", out var jobs ) )
|
|
{
|
|
Console.WriteLine( "Jobs property not found in API response" );
|
|
return jobId;
|
|
}
|
|
|
|
// First pass: try to find exact job name match
|
|
foreach ( var job in jobs.EnumerateArray() )
|
|
{
|
|
if ( job.TryGetProperty( "name", out var nameElement ) &&
|
|
string.Equals( nameElement.GetString(), jobName, StringComparison.Ordinal ) )
|
|
{
|
|
jobId = job.GetProperty( "id" ).GetInt64();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Second pass: try case-insensitive match if exact match failed
|
|
if ( jobId == -1 )
|
|
{
|
|
foreach ( var job in jobs.EnumerateArray() )
|
|
{
|
|
if ( job.TryGetProperty( "name", out var nameElement ) &&
|
|
string.Equals( nameElement.GetString(), jobName, StringComparison.OrdinalIgnoreCase ) )
|
|
{
|
|
jobId = job.GetProperty( "id" ).GetInt64();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we still couldn't find by name, use the first job
|
|
if ( jobId == -1 && jobs.GetArrayLength() > 0 )
|
|
{
|
|
var firstJob = jobs[0];
|
|
jobId = firstJob.GetProperty( "id" ).GetInt64();
|
|
}
|
|
}
|
|
catch ( Exception ex )
|
|
{
|
|
Console.WriteLine( $"Error getting job ID: {ex.Message}" );
|
|
}
|
|
|
|
return jobId;
|
|
}
|
|
|
|
private static string GetCommitSha()
|
|
{
|
|
string sha;
|
|
var eventName = Environment.GetEnvironmentVariable( "GITHUB_EVENT_NAME" );
|
|
|
|
if ( eventName == "pull_request" )
|
|
{
|
|
// For pull requests, we need to get the head SHA from the event payload
|
|
sha = Environment.GetEnvironmentVariable( "GITHUB_EVENT_PULL_REQUEST_HEAD_SHA" );
|
|
|
|
if ( string.IsNullOrEmpty( sha ) )
|
|
{
|
|
// Try to get it from the event file
|
|
try
|
|
{
|
|
var eventPath = Environment.GetEnvironmentVariable( "GITHUB_EVENT_PATH" );
|
|
if ( !string.IsNullOrEmpty( eventPath ) )
|
|
{
|
|
var eventJson = System.IO.File.ReadAllText( eventPath );
|
|
using ( var doc = JsonDocument.Parse( eventJson ) )
|
|
{
|
|
sha = doc.RootElement
|
|
.GetProperty( "pull_request" )
|
|
.GetProperty( "head" )
|
|
.GetProperty( "sha" )
|
|
.GetString();
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// If we can't get the SHA from the event file, fall back to GITHUB_SHA
|
|
sha = Environment.GetEnvironmentVariable( "GITHUB_SHA" );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For other events, use the commit SHA
|
|
sha = Environment.GetEnvironmentVariable( "GITHUB_SHA" );
|
|
}
|
|
|
|
if ( string.IsNullOrEmpty( sha ) )
|
|
{
|
|
Console.WriteLine( "Commit SHA not available" );
|
|
}
|
|
|
|
return sha ?? "";
|
|
}
|
|
|
|
private static bool TryGetTokenAndRepositoryFromEnv( out string token, out string repository )
|
|
{
|
|
token = Environment.GetEnvironmentVariable( "GITHUB_TOKEN" ) ?? string.Empty;
|
|
if ( string.IsNullOrEmpty( token ) )
|
|
{
|
|
Console.WriteLine( "GITHUB_TOKEN not set" );
|
|
repository = string.Empty;
|
|
return false;
|
|
}
|
|
|
|
repository = Environment.GetEnvironmentVariable( "GITHUB_REPOSITORY" ) ?? string.Empty;
|
|
if ( string.IsNullOrEmpty( repository ) )
|
|
{
|
|
Console.WriteLine( "GITHUB_REPOSITORY not set" );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static void SetupHttpClient( string token )
|
|
{
|
|
httpClient.DefaultRequestHeaders.Clear();
|
|
httpClient.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue( "application/vnd.github+json" ) );
|
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( "Bearer", token );
|
|
httpClient.DefaultRequestHeaders.Add( "User-Agent", "SboxBuild" );
|
|
httpClient.DefaultRequestHeaders.Add( "X-GitHub-Api-Version", "2022-11-28" );
|
|
}
|
|
}
|