2using System.Collections.Concurrent;
3using System.Collections.Generic;
4using System.Collections.ObjectModel;
5using System.Globalization;
8using System.Threading.Tasks;
10using Microsoft.EntityFrameworkCore;
11using Microsoft.Extensions.Logging;
48 ILogger<GitHubRemoteDeploymentManager> logger,
49 Api.Models.Instance metadata,
59 Api.Models.Internal.IGitRemoteInformation remoteInformation,
61 CancellationToken cancellationToken)
63 ArgumentNullException.ThrowIfNull(remoteInformation);
64 ArgumentNullException.ThrowIfNull(compileJob);
66 Logger.LogTrace(
"Starting deployment...");
70 async databaseContext =>
71 repositorySettings = await databaseContext
73 .Where(x => x.InstanceId ==
Metadata.Id)
74 .FirstAsync(cancellationToken));
76 var instanceAuthenticated = repositorySettings!.
AccessToken !=
null;
79 if (instanceAuthenticated)
86 if (authenticatedGitHubService ==
null)
88 Logger.LogWarning(
"Can't create GitHub deployment as authentication for repository failed!");
92 gitHubService = authenticatedGitHubService;
96 authenticatedGitHubService =
null;
100 var repoOwner = remoteInformation.RemoteRepositoryOwner!;
101 var repoName = remoteInformation.RemoteRepositoryName!;
108 Logger.LogTrace(
"Not creating deployment");
109 else if (!instanceAuthenticated)
110 Logger.LogWarning(
"Can't create GitHub deployment as no access token is set for repository!");
111 else if (authenticatedGitHubService !=
null)
113 Logger.LogTrace(
"Creating deployment...");
116 compileJob.GitHubDeploymentId = await authenticatedGitHubService.
CreateDeployment(
120 Description =
"TGS Game Deployment",
121 Environment = $
"TGS: {Metadata.Name}",
122 ProductionEnvironment = true,
123 RequiredContexts = new Collection<string>(),
132 new NewDeploymentStatus(DeploymentState.InProgress)
134 Description =
"The project is being deployed",
135 AutoInactive = false,
142 Logger.LogTrace(
"In-progress deployment status created");
144 catch (
Exception ex) when (ex is not OperationCanceledException)
146 Logger.LogWarning(ex,
"Unable to create GitHub deployment!");
152 compileJob.GitHubRepoId = await repositoryIdTask;
155 catch (
Exception ex) when (ex is not OperationCanceledException)
157 Logger.LogWarning(ex,
"Unable to set compile job repository ID!");
166 DeploymentState.Error,
174 CancellationToken cancellationToken)
176 ArgumentNullException.ThrowIfNull(repository);
177 ArgumentNullException.ThrowIfNull(repositorySettings);
178 ArgumentNullException.ThrowIfNull(revisionInformation);
182 Logger.LogTrace(
"No test merges to remove.");
186 var gitHubService = repositorySettings.AccessToken !=
null
194 var tasks = revisionInformation
196 .Select(x => gitHubService.GetPullRequest(
203 await Task.WhenAll(tasks);
205 catch (
Exception ex) when (ex is not OperationCanceledException)
207 Logger.LogWarning(ex,
"Pull requests update check failed!");
210 var newList = revisionInformation.
ActiveTestMerges.Select(x => x.TestMerge).ToList();
212 PullRequest? lastMerged =
null;
213 async ValueTask CheckRemovePR(Task<PullRequest> task)
222 if (lastMerged ==
null || lastMerged.MergedAt < pr.MergedAt)
226 potential => potential.Number == pr.Number));
230 foreach (var prTask
in tasks)
231 await CheckRemovePR(prTask);
239 CancellationToken cancellationToken)
242 "The deployment succeeded and will be applied a the next server reboot.",
243 DeploymentState.Pending,
250 "The deployment is now live on the server.",
251 DeploymentState.Success,
258 "The deployment has been superceeded.",
259 DeploymentState.Inactive,
265 string remoteRepositoryOwner,
266 string remoteRepositoryName,
269 CancellationToken cancellationToken)
276 if (gitHubService ==
null)
278 Logger.LogWarning(
"Error posting GitHub comment: Authentication failed!");
284 string header = String.Format(CultureInfo.InvariantCulture,
"{1}{0}## Test merge deployment history:{0}{0}", Environment.NewLine,
DeploymentMsgHeaderStart);
286 var existingComment = await gitHubService.GetExistingCommentOnIssue(remoteRepositoryOwner, remoteRepositoryName,
DeploymentMsgHeaderStart, testMergeNumber, cancellationToken);
287 if (existingComment !=
null)
289 await gitHubService.AppendCommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, comment, existingComment, cancellationToken);
293 await gitHubService.CommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, header + comment, testMergeNumber, cancellationToken);
296 catch (
Exception ex) when (ex is not OperationCanceledException)
298 Logger.LogWarning(ex,
"Error posting GitHub comment!");
307 string remoteRepositoryOwner,
308 string remoteRepositoryName,
309 bool updated) => String.Format(
310 CultureInfo.InvariantCulture,
311 "<details><summary>Test Merge {4} @ {8}:</summary>{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}{0}</details>{0}",
313 repositorySettings.ShowTestMergeCommitters!.Value
315 CultureInfo.InvariantCulture,
316 "{0}{0}##### Merged By{0}{1}",
318 testMerge.MergedBy!.Name)
320 testMerge.TargetCommitSha,
321 String.IsNullOrEmpty(testMerge.Comment)
324 CultureInfo.InvariantCulture,
325 "{0}{0}##### Comment{0}{1}",
328 updated ?
"Updated" :
"Deployed",
329 compileJob.GitHubDeploymentId.HasValue
330 ? $
"{Environment.NewLine}[{Metadata.Name}](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A+{Metadata.Name!.Replace(" ", "+
", StringComparison.Ordinal)})"
334 compileJob.
Job.StartedAt);
341 string remoteRepositoryOwner,
342 string remoteRepositoryName) => String.Format(
343 CultureInfo.InvariantCulture,
344 "<details><summary>Test Merge Removed @ {2}:</summary>{0}{0}##### Server Instance{0}{1}{0}</details>{0}",
347 compileJob.Job.StartedAt);
360 DeploymentState deploymentState,
361 CancellationToken cancellationToken)
363 ArgumentNullException.ThrowIfNull(compileJob);
367 Logger.LogTrace(
"Not updating deployment as it is missing a repo ID or deployment ID.");
371 Logger.LogTrace(
"Updating deployment {gitHubDeploymentId} to {deploymentState}...", compileJob.
GitHubDeploymentId.Value, deploymentState);
373 string? gitHubAccessToken =
null;
375 async databaseContext =>
376 gitHubAccessToken = await databaseContext
378 .Where(x => x.InstanceId ==
Metadata.Id)
379 .Select(x => x.AccessToken)
380 .FirstAsync(cancellationToken));
382 if (gitHubAccessToken ==
null)
385 "GitHub access token disappeared during deployment, can't update to {deploymentState}!",
395 if (gitHubService ==
null)
398 "GitHub authentication failed, can't update to {deploymentState}!",
405 await gitHubService.CreateDeploymentStatus(
406 new NewDeploymentStatus(deploymentState)
408 Description = description,
414 catch (
Exception ex) when (ex is not OperationCanceledException)
416 Logger.LogWarning(ex,
"Error updating GitHub deployment!");
bool? CreateGitHubDeployments
If GitHub deployments should be created. Requires AccessUser, AccessToken, and PushTestMergeCommits t...
string? AccessToken
The token/password to access the git repository with. Can also be a TGS encoded app private key....
Base class for implementing IRemoteDeploymentManagers.
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.
IRemoteDeploymentManager for GitHub.com.
override async ValueTask CommentOnTestMergeSource(RepositorySettings repositorySettings, string remoteRepositoryOwner, string remoteRepositoryName, string comment, int testMergeNumber, CancellationToken cancellationToken)
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the GitHubRemoteDeploymentManager.
readonly IGitHubServiceFactory gitHubServiceFactory
The IGitHubServiceFactory for the GitHubRemoteDeploymentManager.
async ValueTask UpdateDeployment(CompileJob compileJob, string description, DeploymentState deploymentState, CancellationToken cancellationToken)
Update the deployment for a given compileJob .
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 string FormatTestMergeRemoval(RepositorySettings repositorySettings, CompileJob compileJob, TestMerge testMerge, string remoteRepositoryOwner, string remoteRepositoryName)
GitHubRemoteDeploymentManager(IDatabaseContextFactory databaseContextFactory, IGitHubServiceFactory gitHubServiceFactory, ILogger< GitHubRemoteDeploymentManager > logger, Api.Models.Instance metadata, ConcurrentDictionary< long, Action< bool > > activationCallbacks)
Initializes a new instance of the GitHubRemoteDeploymentManager class.
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.
override async ValueTask StartDeployment(Api.Models.Internal.IGitRemoteInformation remoteInformation, CompileJob compileJob, CancellationToken cancellationToken)
Start a deployment for a given compileJob .A ValueTask representing the running operation.
override string FormatTestMerge(RepositorySettings repositorySettings, CompileJob compileJob, TestMerge testMerge, string remoteRepositoryOwner, string remoteRepositoryName, bool updated)
override ValueTask StageDeploymentImpl(CompileJob compileJob, CancellationToken cancellationToken)
override ValueTask ApplyDeploymentImpl(CompileJob compileJob, CancellationToken cancellationToken)
RevisionInformation RevisionInformation
See CompileJobResponse.RevisionInformation.
long? GitHubDeploymentId
The GitHub deployment ID associated with the CompileJob if any.
long? GitHubRepoId
The source GitHub repository the deployment came from if any.
Identifies a repository either by its RepositoryId or Owner and Name.
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.
Factory for scoping usage of IDatabaseContexts. Meant for use by Components.
ValueTask UseContext(Func< IDatabaseContext, ValueTask > operation)
Run an operation in the scope of an IDatabaseContext.
IGitHubService that exposes functions that require authentication.
ValueTask< long > CreateDeployment(NewDeployment newDeployment, string repoOwner, string repoName, CancellationToken cancellationToken)
Create a newDeployment on a target repostiory.
Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, string repoOwner, string repoName, long deploymentId, CancellationToken cancellationToken)
Create a newDeploymentStatus on a target deployment.
Factory for IGitHubServices.
ValueTask< IGitHubService > CreateService(CancellationToken cancellationToken)
Create a IGitHubService.
Service for interacting with the GitHub API.
ValueTask< long > GetRepositoryId(string repoOwner, string repoName, CancellationToken cancellationToken)
Get a target repostiory's ID.