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
74 .Where(x => x.InstanceId ==
Metadata.Id)
75 .FirstAsync(cancellationToken));
77 var instanceAuthenticated = repositorySettings!.
AccessToken !=
null;
80 if (instanceAuthenticated)
87 if (authenticatedGitHubService ==
null)
89 Logger.LogWarning(
"Can't create GitHub deployment as authentication for repository failed!");
93 gitHubService = authenticatedGitHubService;
97 authenticatedGitHubService =
null;
101 var repoOwner = remoteInformation.RemoteRepositoryOwner!;
102 var repoName = remoteInformation.RemoteRepositoryName!;
109 Logger.LogTrace(
"Not creating deployment");
110 else if (!instanceAuthenticated)
111 Logger.LogWarning(
"Can't create GitHub deployment as no access token is set for repository!");
112 else if (authenticatedGitHubService !=
null)
114 Logger.LogTrace(
"Creating deployment...");
117 compileJob.GitHubDeploymentId = await authenticatedGitHubService.
CreateDeployment(
121 Description =
"TGS Game Deployment",
122 Environment = $
"TGS: {Metadata.Name}",
123 ProductionEnvironment = true,
124 RequiredContexts = new Collection<string>(),
133 new NewDeploymentStatus(DeploymentState.InProgress)
135 Description =
"The project is being deployed",
136 AutoInactive = false,
143 Logger.LogTrace(
"In-progress deployment status created");
145 catch (
Exception ex) when (ex is not OperationCanceledException)
147 Logger.LogWarning(ex,
"Unable to create GitHub deployment!");
153 compileJob.GitHubRepoId = await repositoryIdTask;
156 catch (
Exception ex) when (ex is not OperationCanceledException)
158 Logger.LogWarning(ex,
"Unable to set compile job repository ID!");
167 DeploymentState.Error,
175 CancellationToken cancellationToken)
177 ArgumentNullException.ThrowIfNull(repository);
178 ArgumentNullException.ThrowIfNull(repositorySettings);
179 ArgumentNullException.ThrowIfNull(revisionInformation);
183 Logger.LogTrace(
"No test merges to remove.");
187 var gitHubService = repositorySettings.AccessToken !=
null
195 var tasks = revisionInformation
197 .Select(x => gitHubService.GetPullRequest(
204 await Task.WhenAll(tasks);
206 catch (
Exception ex) when (ex is not OperationCanceledException)
208 Logger.LogWarning(ex,
"Pull requests update check failed!");
211 var newList = revisionInformation.
ActiveTestMerges.Select(x => x.TestMerge).ToList();
213 PullRequest? lastMerged =
null;
214 async ValueTask CheckRemovePR(Task<PullRequest> task)
223 if (lastMerged ==
null || lastMerged.MergedAt < pr.MergedAt)
227 potential => potential.Number == pr.Number));
231 foreach (var prTask
in tasks)
232 await CheckRemovePR(prTask);
240 CancellationToken cancellationToken)
243 "The deployment succeeded and will be applied a the next server reboot.",
244 DeploymentState.Pending,
251 "The deployment is now live on the server.",
252 DeploymentState.Success,
259 "The deployment has been superceeded.",
260 DeploymentState.Inactive,
266 string remoteRepositoryOwner,
267 string remoteRepositoryName,
270 CancellationToken cancellationToken)
277 if (gitHubService ==
null)
279 Logger.LogWarning(
"Error posting GitHub comment: Authentication failed!");
285 string header = String.Format(CultureInfo.InvariantCulture,
"{1}{0}## Test merge deployment history:{0}{0}", Environment.NewLine,
DeploymentMsgHeaderStart);
287 var existingComment = await gitHubService.GetExistingCommentOnIssue(remoteRepositoryOwner, remoteRepositoryName,
DeploymentMsgHeaderStart, testMergeNumber, cancellationToken);
288 if (existingComment !=
null)
290 await gitHubService.AppendCommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, comment, existingComment, cancellationToken);
294 await gitHubService.CommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, header + comment, testMergeNumber, cancellationToken);
297 catch (
Exception ex) when (ex is not OperationCanceledException)
299 Logger.LogWarning(ex,
"Error posting GitHub comment!");
308 string remoteRepositoryOwner,
309 string remoteRepositoryName,
310 bool updated) => String.Format(
311 CultureInfo.InvariantCulture,
312 "<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}",
314 repositorySettings.ShowTestMergeCommitters!.Value
316 CultureInfo.InvariantCulture,
317 "{0}{0}##### Merged By{0}{1}",
319 testMerge.MergedBy!.Name)
321 testMerge.TargetCommitSha,
322 String.IsNullOrEmpty(testMerge.Comment)
325 CultureInfo.InvariantCulture,
326 "{0}{0}##### Comment{0}{1}",
329 updated ?
"Updated" :
"Deployed",
330 compileJob.GitHubDeploymentId.HasValue
331 ? $
"{Environment.NewLine}[{Metadata.Name}](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A+{Metadata.Name!.Replace(" ", "+
", StringComparison.Ordinal)})"
335 compileJob.
Job.StartedAt);
342 string remoteRepositoryOwner,
343 string remoteRepositoryName) => String.Format(
344 CultureInfo.InvariantCulture,
345 "<details><summary>Test Merge Removed @ {2}:</summary>{0}{0}##### Server Instance{0}{1}{0}</details>{0}",
348 compileJob.Job.StartedAt);
361 DeploymentState deploymentState,
362 CancellationToken cancellationToken)
364 ArgumentNullException.ThrowIfNull(compileJob);
368 Logger.LogTrace(
"Not updating deployment as it is missing a repo ID or deployment ID.");
372 Logger.LogTrace(
"Updating deployment {gitHubDeploymentId} to {deploymentState}...", compileJob.
GitHubDeploymentId.Value, deploymentState);
374 string? gitHubAccessToken =
null;
376 async databaseContext =>
377 gitHubAccessToken = await databaseContext
380 .Where(x => x.InstanceId ==
Metadata.Id)
381 .Select(x => x.AccessToken)
382 .FirstAsync(cancellationToken));
384 if (gitHubAccessToken ==
null)
387 "GitHub access token disappeared during deployment, can't update to {deploymentState}!",
397 if (gitHubService ==
null)
400 "GitHub authentication failed, can't update to {deploymentState}!",
407 await gitHubService.CreateDeploymentStatus(
408 new NewDeploymentStatus(deploymentState)
410 Description = description,
416 catch (
Exception ex) when (ex is not OperationCanceledException)
418 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.