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)
83 gitHubService = authenticatedGitHubService;
87 authenticatedGitHubService =
null;
91 var repoOwner = remoteInformation.RemoteRepositoryOwner!;
92 var repoName = remoteInformation.RemoteRepositoryName!;
99 Logger.LogTrace(
"Not creating deployment");
100 else if (!instanceAuthenticated)
101 Logger.LogWarning(
"Can't create GitHub deployment as no access token is set for repository!");
104 Logger.LogTrace(
"Creating deployment...");
107 compileJob.GitHubDeploymentId = await authenticatedGitHubService!.
CreateDeployment(
111 Description =
"TGS Game Deployment",
112 Environment = $
"TGS: {Metadata.Name}",
113 ProductionEnvironment = true,
114 RequiredContexts = new Collection<string>(),
123 new NewDeploymentStatus(DeploymentState.InProgress)
125 Description =
"The project is being deployed",
126 AutoInactive = false,
133 Logger.LogTrace(
"In-progress deployment status created");
135 catch (
Exception ex) when (ex is not OperationCanceledException)
137 Logger.LogWarning(ex,
"Unable to create GitHub deployment!");
143 compileJob.GitHubRepoId = await repositoryIdTask;
146 catch (
Exception ex) when (ex is not OperationCanceledException)
148 Logger.LogWarning(ex,
"Unable to set compile job repository ID!");
157 DeploymentState.Error,
165 CancellationToken cancellationToken)
167 ArgumentNullException.ThrowIfNull(repository);
168 ArgumentNullException.ThrowIfNull(repositorySettings);
169 ArgumentNullException.ThrowIfNull(revisionInformation);
173 Logger.LogTrace(
"No test merges to remove.");
177 var gitHubService = repositorySettings.AccessToken !=
null
181 var tasks = revisionInformation
183 .Select(x => gitHubService.GetPullRequest(
190 await Task.WhenAll(tasks);
192 catch (
Exception ex) when (ex is not OperationCanceledException)
194 Logger.LogWarning(ex,
"Pull requests update check failed!");
197 var newList = revisionInformation.
ActiveTestMerges.Select(x => x.TestMerge).ToList();
199 PullRequest? lastMerged =
null;
200 async ValueTask CheckRemovePR(Task<PullRequest> task)
209 if (lastMerged ==
null || lastMerged.MergedAt < pr.MergedAt)
213 potential => potential.Number == pr.Number));
217 foreach (var prTask
in tasks)
218 await CheckRemovePR(prTask);
226 CancellationToken cancellationToken)
229 "The deployment succeeded and will be applied a the next server reboot.",
230 DeploymentState.Pending,
237 "The deployment is now live on the server.",
238 DeploymentState.Success,
245 "The deployment has been superceeded.",
246 DeploymentState.Inactive,
252 string remoteRepositoryOwner,
253 string remoteRepositoryName,
256 CancellationToken cancellationToken)
262 await gitHubService.CommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, comment, testMergeNumber, cancellationToken);
264 catch (
Exception ex) when (ex is not OperationCanceledException)
266 Logger.LogWarning(ex,
"Error posting GitHub comment!");
275 string remoteRepositoryOwner,
276 string remoteRepositoryName,
277 bool updated) => String.Format(
278 CultureInfo.InvariantCulture,
279 "#### Test Merge {4}{0}{0}<details><summary>Details</summary>{0}{0}##### Server Instance{0}{5}{1}{0}{0}##### Revision{0}Origin: {6}{0}Pull Request: {2}{0}Server: {7}{3}{8}{0}</details>",
281 repositorySettings.ShowTestMergeCommitters!.Value
283 CultureInfo.InvariantCulture,
284 "{0}{0}##### Merged By{0}{1}",
286 testMerge.MergedBy!.Name)
288 testMerge.TargetCommitSha,
289 testMerge.Comment != null
291 CultureInfo.InvariantCulture,
292 "{0}{0}##### Comment{0}{1}",
296 updated ?
"Updated" :
"Deployed",
300 compileJob.GitHubDeploymentId.HasValue
301 ? $
"{Environment.NewLine}[GitHub Deployments](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A+{Metadata.Name!.Replace(" ", "+
", StringComparison.Ordinal)})"
315 DeploymentState deploymentState,
316 CancellationToken cancellationToken)
318 ArgumentNullException.ThrowIfNull(compileJob);
322 Logger.LogTrace(
"Not updating deployment as it is missing a repo ID or deployment ID.");
326 Logger.LogTrace(
"Updating deployment {gitHubDeploymentId} to {deploymentState}...", compileJob.
GitHubDeploymentId.Value, deploymentState);
328 string? gitHubAccessToken =
null;
330 async databaseContext =>
331 gitHubAccessToken = await databaseContext
334 .Where(x => x.InstanceId ==
Metadata.Id)
335 .Select(x => x.AccessToken)
336 .FirstAsync(cancellationToken));
338 if (gitHubAccessToken ==
null)
341 "GitHub access token disappeared during deployment, can't update to {deploymentState}!",
350 await gitHubService.CreateDeploymentStatus(
351 new NewDeploymentStatus(deploymentState)
353 Description = description,
359 catch (
Exception ex) when (ex is not OperationCanceledException)
361 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.
Base class for implementing IRemoteDeploymentManagers.
Api.Models.Instance Metadata
The Api.Models.Instance for the BaseRemoteDeploymentManager.
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...
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.
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.