tgstation-server 6.16.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
GitLabRemoteDeploymentManager.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Concurrent;
3using System.Collections.Generic;
4using System.Globalization;
5using System.Linq;
6using System.Threading;
7using System.Threading.Tasks;
8
9using Microsoft.Extensions.Logging;
10
11using StrawberryShake;
12
16
18{
23 {
31 ILogger<GitLabRemoteDeploymentManager> logger,
32 Api.Models.Instance metadata,
33 ConcurrentDictionary<long, Action<bool>> activationCallbacks)
34 : base(logger, metadata, activationCallbacks)
35 {
36 }
37
39 public override async ValueTask<IReadOnlyCollection<TestMerge>> RemoveMergedTestMerges(
40 IRepository repository,
41 RepositorySettings repositorySettings,
42 RevisionInformation revisionInformation,
43 CancellationToken cancellationToken)
44 {
45 ArgumentNullException.ThrowIfNull(repository);
46 ArgumentNullException.ThrowIfNull(repositorySettings);
47 ArgumentNullException.ThrowIfNull(revisionInformation);
48
49 if ((revisionInformation.ActiveTestMerges?.Count > 0) != true)
50 {
51 Logger.LogTrace("No test merges to remove.");
52 return Array.Empty<TestMerge>();
53 }
54
55 var newList = revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList();
56
57 await using var client = await GraphQLGitLabClientFactory.CreateClient(repositorySettings.AccessToken);
58 IOperationResult<IGetMergeRequestsResult> operationResult;
59 try
60 {
61 operationResult = await client.GraphQL.GetMergeRequests.ExecuteAsync(
62 $"{repository.RemoteRepositoryOwner}/{repository.RemoteRepositoryName}",
63 revisionInformation.ActiveTestMerges.Select(revInfoTestMerge => revInfoTestMerge.TestMerge.Number.ToString(CultureInfo.InvariantCulture)).ToList(),
64 cancellationToken);
65
66 operationResult.EnsureNoErrors();
67 }
68 catch (Exception ex) when (ex is not OperationCanceledException)
69 {
70 Logger.LogWarning(ex, "Merge requests update check failed!");
71 return newList;
72 }
73
74 var data = operationResult.Data?.Project?.MergeRequests?.Nodes;
75 if (data == null)
76 {
77 Logger.LogWarning("GitLab MergeRequests check returned null!");
78 return newList;
79 }
80
81 async ValueTask CheckRemoveMR(IGetMergeRequests_Project_MergeRequests_Nodes? mergeRequest)
82 {
83 if (mergeRequest == null)
84 {
85 Logger.LogWarning("GitLab MergeRequest node was null!");
86 return;
87 }
88
89 if (mergeRequest.State != MergeRequestState.Merged)
90 return;
91
92 var mergeCommitSha = mergeRequest.MergeCommitSha;
93 if (mergeCommitSha == null)
94 {
95 Logger.LogWarning("MergeRequest #{id} had no MergeCommitSha!", mergeRequest.Iid);
96 return;
97 }
98
99 var closedAtStr = mergeRequest.ClosedAt;
100 if (closedAtStr == null)
101 {
102 Logger.LogWarning("MergeRequest #{id} had no ClosedAt!", mergeRequest.Iid);
103 return;
104 }
105
106 if (!DateTimeOffset.TryParseExact(closedAtStr, "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset closedAt))
107 {
108 Logger.LogWarning("MergeRequest #{id} had invalid ClosedAt: {closedAt}", mergeRequest.Iid, closedAtStr);
109 return;
110 }
111
112 if (!Int64.TryParse(mergeRequest.Iid, out long number))
113 {
114 Logger.LogWarning("MergeRequest #{id} is non-numeric!", mergeRequest.Iid);
115 return;
116 }
117
118 // We don't just assume, actually check the repo contains the merge commit.
119 if (await repository.CommittishIsParent(mergeCommitSha, cancellationToken))
120 newList.Remove(
121 newList.First(
122 potential => potential.Number == number));
123 }
124
125 foreach (var mergeRequest in data)
126 await CheckRemoveMR(mergeRequest);
127
128 return newList;
129 }
130
132 public override ValueTask FailDeployment(
133 CompileJob compileJob,
134 string errorMessage,
135 CancellationToken cancellationToken) => ValueTask.CompletedTask;
136
138 public override ValueTask StartDeployment(
139 Api.Models.Internal.IGitRemoteInformation remoteInformation,
140 CompileJob compileJob,
141 CancellationToken cancellationToken) => ValueTask.CompletedTask;
142
144 protected override ValueTask ApplyDeploymentImpl(
145 CompileJob compileJob,
146 CancellationToken cancellationToken) => ValueTask.CompletedTask;
147
149 protected override ValueTask StageDeploymentImpl(CompileJob compileJob, CancellationToken cancellationToken) => ValueTask.CompletedTask;
150
152 protected override ValueTask MarkInactiveImpl(CompileJob compileJob, CancellationToken cancellationToken) => ValueTask.CompletedTask;
153
155 protected override async ValueTask CommentOnTestMergeSource(
156 RepositorySettings repositorySettings,
157 string remoteRepositoryOwner,
158 string remoteRepositoryName,
159 string comment,
160 int testMergeNumber,
161 CancellationToken cancellationToken)
162 {
163 await using var client = await GraphQLGitLabClientFactory.CreateClient(repositorySettings.AccessToken);
164 try
165 {
166 string header = String.Format(CultureInfo.InvariantCulture, "{1}{0}## Test merge deployment history:{0}{0}", Environment.NewLine, DeploymentMsgHeaderStart);
167
168 // Try to find an existing note
169 var notesQueryResult = await client.GraphQL.GetMergeRequestNotes.ExecuteAsync(
170 $"{remoteRepositoryOwner}/{remoteRepositoryName}",
171 testMergeNumber.ToString(CultureInfo.InvariantCulture),
172 cancellationToken);
173
174 notesQueryResult.EnsureNoErrors();
175
176 var mergeRequest = notesQueryResult.Data?.Project?.MergeRequest;
177 if (mergeRequest == null)
178 {
179 Logger.LogWarning("GitLab GetMergeRequestNotes mergeRequest returned null!");
180 return;
181 }
182
183 var comments = mergeRequest.Notes?.Nodes;
184 IGetMergeRequestNotes_Project_MergeRequest_Notes_Nodes? existingComment = null;
185 if (comments != null)
186 {
187 for (int i = comments.Count - 1; i > -1; i--)
188 {
189 var currentComment = comments[i];
190 if (currentComment?.Author?.Username == repositorySettings.AccessUser && (currentComment?.Body?.StartsWith(DeploymentMsgHeaderStart) ?? false))
191 {
192 if (currentComment.Body.Length > 987856)
193 { // Limit should be 999,999 so we'll leave a 12,143 buffer
194 break;
195 }
196
197 existingComment = currentComment;
198 break;
199 }
200 }
201 }
202
203 // Either amend or create the note
204 if (existingComment != null)
205 {
206 var noteModificationResult = await client.GraphQL.ModifyNote.ExecuteAsync(
207 existingComment.Id,
208 existingComment.Body + comment,
209 cancellationToken);
210
211 notesQueryResult.EnsureNoErrors();
212 }
213 else
214 {
215 var noteCreationResult = await client.GraphQL.CreateNote.ExecuteAsync(
216 mergeRequest.Id,
217 header + comment,
218 cancellationToken);
219
220 noteCreationResult.EnsureNoErrors();
221 }
222 }
223 catch (Exception ex) when (ex is not OperationCanceledException)
224 {
225 Logger.LogWarning(ex, "Error posting GitLab comment!");
226 }
227 }
228
230 protected override string FormatTestMerge(
231 RepositorySettings repositorySettings,
232 CompileJob compileJob,
233 TestMerge testMerge,
234 string remoteRepositoryOwner,
235 string remoteRepositoryName,
236 bool updated) => String.Format(
237 CultureInfo.InvariantCulture,
238 "<details><summary>Test Merge {4} @ {8}</summary>{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Merge Request: {2}{0}Server: {7}{3}</details>{0}",
239 Environment.NewLine, // 0
240 repositorySettings.ShowTestMergeCommitters!.Value
241 ? String.Format(
242 CultureInfo.InvariantCulture,
243 "{0}{0}##### Merged By{0}{1}",
244 Environment.NewLine,
245 testMerge.MergedBy!.Name)
246 : String.Empty, // 1
247 testMerge.TargetCommitSha, // 2
248 String.IsNullOrEmpty(testMerge.Comment)
249 ? String.Empty
250 : String.Format(
251 CultureInfo.InvariantCulture,
252 "{0}{0}##### Comment{0}{1}",
253 Environment.NewLine,
254 testMerge.Comment), // 3
255 updated ? "Updated" : "Deployed", // 4
256 Metadata.Name, // 5
257 compileJob.RevisionInformation.OriginCommitSha, // 6
258 compileJob.RevisionInformation.CommitSha, // 7
259 compileJob.Job.StartedAt); // 8
260
262 protected override string FormatTestMergeRemoval(
263 RepositorySettings repositorySettings,
264 CompileJob compileJob,
265 TestMerge testMerge,
266 string remoteRepositoryOwner,
267 string remoteRepositoryName) => String.Format(
268 CultureInfo.InvariantCulture,
269 "<details><summary>Test Merge Removed @ {2}:</summary>{0}{0}##### Server Instance{0}{1}{0}</details>{0}",
270 Environment.NewLine, // 0
271 Metadata.Name, // 1
272 compileJob.Job.StartedAt); // 2
273 }
274}
string? AccessToken
The token/password to access the git repository with. Can also be a TGS encoded app private key....
string? AccessUser
The username to access the git repository with. If using a TGS encoded app private key for AccessToke...
Api.Models.Instance Metadata
The Api.Models.Instance for the BaseRemoteDeploymentManager.
const string DeploymentMsgHeaderStart
The header comment that begins every deployment message comment/note.
readonly ConcurrentDictionary< long, Action< bool > > activationCallbacks
A map of CompileJob Api.Models.EntityId.Ids to activation callback Action<T1>s.
ILogger< BaseRemoteDeploymentManager > Logger
The ILogger for the BaseRemoteDeploymentManager.
override async ValueTask< IReadOnlyCollection< TestMerge > > RemoveMergedTestMerges(IRepository repository, RepositorySettings repositorySettings, RevisionInformation revisionInformation, CancellationToken cancellationToken)
Get the updated list of TestMerges for an origin merge.A ValueTask<TResult> resulting in the IReadOnl...
override ValueTask ApplyDeploymentImpl(CompileJob compileJob, CancellationToken cancellationToken)
override string FormatTestMerge(RepositorySettings repositorySettings, CompileJob compileJob, TestMerge testMerge, string remoteRepositoryOwner, string remoteRepositoryName, bool updated)
override async ValueTask CommentOnTestMergeSource(RepositorySettings repositorySettings, string remoteRepositoryOwner, string remoteRepositoryName, string comment, int testMergeNumber, CancellationToken cancellationToken)
override ValueTask StageDeploymentImpl(CompileJob compileJob, CancellationToken cancellationToken)
override ValueTask StartDeployment(Api.Models.Internal.IGitRemoteInformation remoteInformation, CompileJob compileJob, CancellationToken cancellationToken)
Start a deployment for a given compileJob .A ValueTask representing the running operation.
GitLabRemoteDeploymentManager(ILogger< GitLabRemoteDeploymentManager > logger, Api.Models.Instance metadata, ConcurrentDictionary< long, Action< bool > > activationCallbacks)
Initializes a new instance of the GitLabRemoteDeploymentManager class.
override string FormatTestMergeRemoval(RepositorySettings repositorySettings, CompileJob compileJob, TestMerge testMerge, string remoteRepositoryOwner, string remoteRepositoryName)
override ValueTask MarkInactiveImpl(CompileJob compileJob, CancellationToken cancellationToken)
override ValueTask FailDeployment(CompileJob compileJob, string errorMessage, CancellationToken cancellationToken)
Fail a deployment for a given compileJob .A ValueTask representing the running operation.
ICollection< RevInfoTestMerge >? ActiveTestMerges
See Api.Models.RevisionInformation.ActiveTestMerges.
static async ValueTask< IGraphQLGitLabClient > CreateClient(string? bearerToken=null)
Sets up a IGraphQLGitLabClient.
Represents an on-disk git repository.
Task< bool > CommittishIsParent(string committish, CancellationToken cancellationToken)
Check if a given committish is a parent of the current Head.