Gitlab graphql transition

Gitlab comment consolidation
This commit is contained in:
Drulikar
2025-02-14 03:31:23 -06:00
parent 699eb34059
commit 5cd2be6cba
11 changed files with 222 additions and 84 deletions
+20
View File
@@ -0,0 +1,20 @@
{
"projects": [
{
"schema": [
"src/Tgstation.Server.Client.GraphQL/schema.graphql"
],
"documents": [
"src/Tgstation.Server.Client.GraphQL/**/*.graphql"
]
},
{
"schema": [
"src/Tgstation.Server.Host.Utils.GitLab.GraphQL/schema.graphql"
],
"documents": [
"src/Tgstation.Server.Host.Utils.GitLab.GraphQL/**/*.graphql"
]
}
]
}
@@ -0,0 +1,12 @@
mutation CreateNote($id: NoteableID!, $body: String!) {
createNote(input: { noteableId: $id, body: $body }) {
note {
id
body
discussion {
id
}
}
errors
}
}
@@ -0,0 +1,9 @@
mutation ModifyNote($id: NoteID!, $body: String!) {
updateNote(input: { id: $id, body: $body }) {
note {
id
body
}
errors
}
}
@@ -0,0 +1,6 @@
query GetCurrentUser {
currentUser
{
username
}
}
@@ -0,0 +1,13 @@
query GetMergeRequest($project: ID!, $number: String!) {
project(fullPath: $project) {
mergeRequest(iid: $number) {
author { username }
description
title
diffHeadSha
webUrl
iid
id
}
}
}
@@ -0,0 +1,15 @@
query GetMergeRequestNotes($project: ID!, $number: String!) {
project(fullPath: $project) {
mergeRequest(iid: $number) {
iid
id
notes {
nodes {
author { username }
body
id
}
}
}
}
}
@@ -3,9 +3,10 @@ query GetMergeRequests($project: ID!, $numbers: [String!]!) {
mergeRequests(iids: $numbers) {
nodes {
state
mergeCommitSha
diffHeadSha
closedAt
iid
id
}
}
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../build/SrcCommon.props" />
<PropertyGroup>
@@ -33,7 +33,12 @@
</Target>
<ItemGroup>
<GraphQL Remove="gql\queries\GetMergeRequests.graphql" />
<GraphQL Remove="GQL\Mutations\CreateNote.graphql" />
<GraphQL Remove="GQL\Mutations\ModifyNote.graphql" />
<GraphQL Remove="GQL\Queries\GetCurrentUser.graphql" />
<GraphQL Remove="GQL\Queries\GetMergeRequest.graphql" />
<GraphQL Remove="GQL\Queries\GetMergeRequestNotes.graphql" />
<GraphQL Remove="GQL\Queries\GetMergeRequests.graphql" />
</ItemGroup>
<ItemGroup>
@@ -42,6 +47,7 @@
<ItemGroup>
<Folder Include="GQL\Queries\" />
<Folder Include="GQL\Mutations\" />
</ItemGroup>
</Project>
@@ -6,9 +6,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GitLabApiClient;
using GitLabApiClient.Models.Notes.Requests;
using Microsoft.Extensions.Logging;
using StrawberryShake;
@@ -92,10 +89,10 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
if (mergeRequest.State != MergeRequestState.Merged)
return;
var mergeCommitSha = mergeRequest.MergeCommitSha;
if (mergeCommitSha == null)
var diffHeadSha = mergeRequest.DiffHeadSha;
if (diffHeadSha == null)
{
Logger.LogWarning("MergeRequest #{id} had no MergeCommitSha!", mergeRequest.Iid);
Logger.LogWarning("MergeRequest #{id} had no DiffHeadSha!", mergeRequest.Iid);
return;
}
@@ -119,7 +116,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
}
// We don't just assume, actually check the repo contains the merge commit.
if (await repository.CommittishIsParent(mergeCommitSha, cancellationToken))
if (await repository.CommittishIsParent(diffHeadSha, cancellationToken))
newList.Remove(
newList.First(
potential => potential.Number == number));
@@ -163,19 +160,65 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
int testMergeNumber,
CancellationToken cancellationToken)
{
var client = repositorySettings.AccessToken != null
? new GitLabClient(GitLabRemoteFeatures.GitLabUrl, repositorySettings.AccessToken)
: new GitLabClient(GitLabRemoteFeatures.GitLabUrl);
await using var client = await GraphQLGitLabClientFactory.CreateClient(repositorySettings.AccessToken);
try
{
await client
.MergeRequests
.CreateNoteAsync(
$"{remoteRepositoryOwner}/{remoteRepositoryName}",
testMergeNumber,
new CreateMergeRequestNoteRequest(comment))
.WaitAsync(cancellationToken);
string header = String.Format(CultureInfo.InvariantCulture, "<!-- test_merge_tgs_bot -->{0}## Test merge deployment history:{0}{0}", Environment.NewLine);
// Try to find an existing note
var notesQueryResult = await client.GraphQL.GetMergeRequestNotes.ExecuteAsync(
$"{remoteRepositoryOwner}/{remoteRepositoryName}",
testMergeNumber.ToString(CultureInfo.InvariantCulture),
cancellationToken);
notesQueryResult.EnsureNoErrors();
var mergeRequest = notesQueryResult.Data?.Project?.MergeRequest;
if (mergeRequest == null)
{
Logger.LogWarning("GitLab GetMergeRequestNotes mergeRequest returned null!");
return;
}
var comments = mergeRequest.Notes?.Nodes;
IGetMergeRequestNotes_Project_MergeRequest_Notes_Nodes? existingComment = null;
if (comments != null)
{
for (int i = comments.Count - 1; i > -1; i--)
{
var currentComment = comments[i];
if (currentComment?.Author?.Username == repositorySettings.AccessUser && (currentComment?.Body?.StartsWith(header) ?? false))
{
if (currentComment.Body.Length > 987856)
{ // Limit should be 999,999 so we'll leave a 12,143 buffer
break;
}
existingComment = currentComment;
break;
}
}
}
// Either amend or create the note
if (existingComment != null)
{
var noteModificationResult = await client.GraphQL.ModifyNote.ExecuteAsync(
existingComment.Id,
existingComment.Body + comment,
cancellationToken);
notesQueryResult.EnsureNoErrors();
}
else
{
var noteCreationResult = await client.GraphQL.CreateNote.ExecuteAsync(
mergeRequest.Id,
header + comment,
cancellationToken);
noteCreationResult.EnsureNoErrors();
}
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
@@ -1,11 +1,14 @@
using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using GitLabApiClient;
using Microsoft.Extensions.Logging;
using StrawberryShake;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Utils.GitLab.GraphQL;
namespace Tgstation.Server.Host.Components.Repository
{
@@ -45,30 +48,25 @@ namespace Tgstation.Server.Host.Components.Repository
RepositorySettings repositorySettings,
CancellationToken cancellationToken)
{
var client = repositorySettings.AccessToken != null
? new GitLabClient(GitLabUrl, repositorySettings.AccessToken)
: new GitLabClient(GitLabUrl);
await using var client = await GraphQLGitLabClientFactory.CreateClient(repositorySettings.AccessToken);
try
{
var mr = await client
.MergeRequests
.GetAsync($"{RemoteRepositoryOwner}/{RemoteRepositoryName}", parameters.Number)
.WaitAsync(cancellationToken);
var operationResult = await client.GraphQL.GetMergeRequest.ExecuteAsync(
$"{RemoteRepositoryOwner}/{RemoteRepositoryName}",
parameters.Number.ToString(CultureInfo.InvariantCulture),
cancellationToken);
var revisionToUse = parameters.TargetCommitSha == null
|| mr.Sha.StartsWith(parameters.TargetCommitSha, StringComparison.OrdinalIgnoreCase)
? mr.Sha
: parameters.TargetCommitSha;
operationResult.EnsureNoErrors();
var mr = operationResult.Data?.Project?.MergeRequest ?? throw new InvalidOperationException("GitLab MergeRequest check returned null!");
return new Models.TestMerge
{
Author = mr.Author.Username,
Author = mr.Author?.Username,
BodyAtMerge = mr.Description,
TitleAtMerge = mr.Title,
Comment = parameters.Comment,
Number = parameters.Number,
TargetCommitSha = mr.Sha,
TargetCommitSha = mr.DiffHeadSha,
Url = mr.WebUrl,
};
}
@@ -7,12 +7,12 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using GitLabApiClient;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using StrawberryShake;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Request;
@@ -27,6 +27,7 @@ using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Utils.GitHub;
using Tgstation.Server.Host.Utils.GitLab.GraphQL;
namespace Tgstation.Server.Host.Controllers
{
@@ -585,70 +586,84 @@ namespace Tgstation.Server.Host.Controllers
switch (remoteFeatures.RemoteGitProvider!.Value)
{
case RemoteGitProvider.GitHub:
var gitHubClient = await gitHubClientFactory.CreateClientForRepository(
{
var gitHubClient = await gitHubClientFactory.CreateClientForRepository(
model.AccessToken,
new RepositoryIdentifier(
remoteFeatures.RemoteRepositoryOwner!,
remoteFeatures.RemoteRepositoryName!),
cancellationToken);
if (gitHubClient == null)
{
return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError)
if (gitHubClient == null)
{
AdditionalData = "GitHub authentication failed!",
});
}
try
{
string username;
if (!model.AccessToken.StartsWith(Api.Models.RepositorySettings.TgsAppPrivateKeyPrefix))
{
var user = await gitHubClient.User.Current();
username = user.Login;
}
else
{
// we literally need to app auth again to get the damn bot username
var appClient = gitHubClientFactory.CreateAppClient(model.AccessToken)!;
var app = await appClient.GitHubApps.GetCurrent();
username = app.Name;
return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError)
{
AdditionalData = "GitHub authentication failed!",
});
}
if (username != model.AccessUser)
return Conflict(new ErrorMessageResponse(ErrorCode.RepoTokenUsernameMismatch));
}
catch (Exception ex)
{
return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError)
try
{
AdditionalData = $"GitHub Authentication Failure: {ex.Message}",
});
string username;
if (!model.AccessToken.StartsWith(Api.Models.RepositorySettings.TgsAppPrivateKeyPrefix))
{
var user = await gitHubClient.User.Current();
username = user.Login;
}
else
{
// we literally need to app auth again to get the damn bot username
var appClient = gitHubClientFactory.CreateAppClient(model.AccessToken)!;
var app = await appClient.GitHubApps.GetCurrent();
username = app.Name;
}
if (username != model.AccessUser)
return Conflict(new ErrorMessageResponse(ErrorCode.RepoTokenUsernameMismatch));
}
catch (Exception ex)
{
return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError)
{
AdditionalData = $"GitHub Authentication Failure: {ex.Message}",
});
}
break;
}
break;
case RemoteGitProvider.GitLab:
// need to abstract this eventually
var gitLabClient = new GitLabClient(GitLabRemoteFeatures.GitLabUrl, model.AccessToken);
try
{
var user = await gitLabClient.Users.GetCurrentSessionAsync();
if (user.Username != model.AccessUser)
return Conflict(new ErrorMessageResponse(ErrorCode.RepoTokenUsernameMismatch));
}
catch (Exception ex)
{
return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError)
// need to abstract this eventually
await using var gitLabClient = await GraphQLGitLabClientFactory.CreateClient(model.AccessToken);
try
{
AdditionalData = $"GitLab Authentication Failure: {ex.Message}",
});
var operationResult = await gitLabClient.GraphQL.GetCurrentUser.ExecuteAsync(cancellationToken);
operationResult.EnsureNoErrors();
var user = operationResult.Data?.CurrentUser;
if (user == null || user.Username != model.AccessUser)
{
return Conflict(new ErrorMessageResponse(ErrorCode.RepoTokenUsernameMismatch));
}
}
catch (Exception ex)
{
return this.StatusCode(HttpStatusCode.FailedDependency, new ErrorMessageResponse(ErrorCode.RemoteApiError)
{
AdditionalData = $"GitLab Authentication Failure: {ex.Message}",
});
}
break;
}
break;
case RemoteGitProvider.Unknown:
default:
Logger.LogWarning("RemoteGitProvider is {provider}, no auth check implemented!", remoteFeatures.RemoteGitProvider.Value);
break;
{
Logger.LogWarning("RemoteGitProvider is {provider}, no auth check implemented!", remoteFeatures.RemoteGitProvider.Value);
break;
}
}
return null;