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 await gitHubService.CommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, comment, testMergeNumber, cancellationToken);
287 catch (
Exception ex) when (ex is not OperationCanceledException)
289 Logger.LogWarning(ex,
"Error posting GitHub comment!");
298 string remoteRepositoryOwner,
299 string remoteRepositoryName,
300 bool updated) => String.Format(
301 CultureInfo.InvariantCulture,
302 "#### 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>",
304 repositorySettings.ShowTestMergeCommitters!.Value
306 CultureInfo.InvariantCulture,
307 "{0}{0}##### Merged By{0}{1}",
309 testMerge.MergedBy!.Name)
311 testMerge.TargetCommitSha,
312 testMerge.Comment != null
314 CultureInfo.InvariantCulture,
315 "{0}{0}##### Comment{0}{1}",
319 updated ?
"Updated" :
"Deployed",
323 compileJob.GitHubDeploymentId.HasValue
324 ? $
"{Environment.NewLine}[GitHub Deployments](https://github.com/{remoteRepositoryOwner}/{remoteRepositoryName}/deployments/activity_log?environment=TGS%3A+{Metadata.Name!.Replace(" ", "+
", StringComparison.Ordinal)})"
338 DeploymentState deploymentState,
339 CancellationToken cancellationToken)
341 ArgumentNullException.ThrowIfNull(compileJob);
345 Logger.LogTrace(
"Not updating deployment as it is missing a repo ID or deployment ID.");
349 Logger.LogTrace(
"Updating deployment {gitHubDeploymentId} to {deploymentState}...", compileJob.
GitHubDeploymentId.Value, deploymentState);
351 string? gitHubAccessToken =
null;
353 async databaseContext =>
354 gitHubAccessToken = await databaseContext
357 .Where(x => x.InstanceId ==
Metadata.Id)
358 .Select(x => x.AccessToken)
359 .FirstAsync(cancellationToken));
361 if (gitHubAccessToken ==
null)
364 "GitHub access token disappeared during deployment, can't update to {deploymentState}!",
374 if (gitHubService ==
null)
377 "GitHub authentication failed, can't update to {deploymentState}!",
384 await gitHubService.CreateDeploymentStatus(
385 new NewDeploymentStatus(deploymentState)
387 Description = description,
393 catch (
Exception ex) when (ex is not OperationCanceledException)
395 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.
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.
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.