tgstation-server 6.16.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
GitHubService.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Threading;
5using System.Threading.Tasks;
6
7using Microsoft.Extensions.Logging;
8
9using Octokit;
10
12
14{
19 {
23 readonly IGitHubClient gitHubClient;
24
28 readonly ILogger<GitHubService> logger;
29
34
41 public GitHubService(IGitHubClient gitHubClient, ILogger<GitHubService> logger, UpdatesConfiguration updatesConfiguration)
42 {
43 this.gitHubClient = gitHubClient ?? throw new ArgumentNullException(nameof(gitHubClient));
44 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
45 this.updatesConfiguration = updatesConfiguration ?? throw new ArgumentNullException(nameof(updatesConfiguration));
46 }
47
49 public async ValueTask<string> CreateOAuthAccessToken(OAuthConfiguration oAuthConfiguration, string code, CancellationToken cancellationToken)
50 {
51 ArgumentNullException.ThrowIfNull(oAuthConfiguration);
52
53 ArgumentNullException.ThrowIfNull(code);
54
55 logger.LogTrace("CreateOAuthAccessToken");
56
57 var response = await gitHubClient
58 .Oauth
59 .CreateAccessToken(
60 new OauthTokenRequest(
61 oAuthConfiguration.ClientId,
62 oAuthConfiguration.ClientSecret,
63 code)
64 {
65 RedirectUri = oAuthConfiguration.RedirectUrl,
66 })
67 .WaitAsync(cancellationToken);
68
69 var token = response.AccessToken;
70 return token;
71 }
72
74 public async ValueTask<Dictionary<Version, Release>> GetTgsReleases(CancellationToken cancellationToken)
75 {
76 logger.LogTrace("GetTgsReleases");
77 var allReleases = await gitHubClient
78 .Repository
79 .Release
81 .WaitAsync(cancellationToken);
82
83 var gitPrefix = updatesConfiguration.GitTagPrefix ?? String.Empty;
84
85 logger.LogTrace("{totalReleases} total releases", allReleases.Count);
86 var releases = allReleases!
87 .Where(release =>
88 {
89 if (!release.PublishedAt.HasValue)
90 {
91 logger.LogDebug("Release tag without PublishedAt: {releaseTag}", release.TagName);
92 return false;
93 }
94
95 if (!release.TagName.StartsWith(gitPrefix, StringComparison.InvariantCulture))
96 return false;
97
98 return true;
99 })
100 .GroupBy(release =>
101 {
102 if (!Version.TryParse(release.TagName.Replace(gitPrefix, String.Empty, StringComparison.Ordinal), out var version))
103 {
104 logger.LogDebug("Unparsable release tag: {releaseTag}", release.TagName);
105 return null;
106 }
107
108 return version;
109 })
110 .Where(grouping => grouping.Key != null)
111
112 // GitHub can return the same result twice or some other nonsense
113 .Select(grouping => Tuple.Create(grouping.Key!, grouping.OrderBy(x => x.PublishedAt ?? DateTimeOffset.MinValue).First()))
114 .ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);
115
116 logger.LogTrace("{parsedReleases} parsed releases", releases.Count);
117 return releases;
118 }
119
121 public async ValueTask<Uri> GetUpdatesRepositoryUrl(CancellationToken cancellationToken)
122 {
123 logger.LogTrace("GetUpdatesRepositoryUrl");
124 var repository = await gitHubClient
125 .Repository
127 .WaitAsync(cancellationToken);
128
129 var repoUrl = new Uri(repository.HtmlUrl);
130 logger.LogTrace("Maps to {repostioryUrl}", repoUrl);
131
132 return repoUrl;
133 }
134
136 public async ValueTask<long> GetCurrentUserId(CancellationToken cancellationToken)
137 {
138 logger.LogTrace("CreateOAuthAccessToken");
139
140 var userDetails = await gitHubClient.User.Current().WaitAsync(cancellationToken);
141 return userDetails.Id;
142 }
143
145 public Task CommentOnIssue(string repoOwner, string repoName, string comment, int issueNumber, CancellationToken cancellationToken)
146 {
147 ArgumentNullException.ThrowIfNull(repoOwner);
148
149 ArgumentNullException.ThrowIfNull(repoName);
150
151 ArgumentNullException.ThrowIfNull(comment);
152
153 logger.LogTrace("CommentOnIssue");
154
155 return gitHubClient
156 .Issue
157 .Comment
158 .Create(
159 repoOwner,
160 repoName,
161 issueNumber,
162 comment)
163 .WaitAsync(cancellationToken);
164 }
165
167 public Task AppendCommentOnIssue(string repoOwner, string repoName, string comment, IssueComment issueComment, CancellationToken cancellationToken)
168 {
169 ArgumentNullException.ThrowIfNull(repoOwner);
170
171 ArgumentNullException.ThrowIfNull(repoName);
172
173 ArgumentNullException.ThrowIfNull(comment);
174
175 ArgumentNullException.ThrowIfNull(issueComment);
176
177 logger.LogTrace("AppendCommentOnIssue");
178
179 return gitHubClient
180 .Issue
181 .Comment
182 .Update(
183 repoOwner,
184 repoName,
185 issueComment.Id,
186 issueComment.Body + comment)
187 .WaitAsync(cancellationToken);
188 }
189
191 public async ValueTask<IssueComment?> GetExistingCommentOnIssue(string repoOwner, string repoName, string header, int issueNumber, CancellationToken cancellationToken)
192 {
193 ArgumentNullException.ThrowIfNull(repoOwner);
194
195 ArgumentNullException.ThrowIfNull(repoName);
196
197 ArgumentNullException.ThrowIfNull(header);
198
199 logger.LogTrace("GetExistingCommentOnIssue");
200
201 var comments = await gitHubClient
202 .Issue
203 .Comment
204 .GetAllForIssue(
205 repoOwner,
206 repoName,
207 issueNumber)
208 .WaitAsync(cancellationToken);
209 if (comments == null)
210 {
211 return null;
212 }
213
214 long userId = await GetCurrentUserId(cancellationToken);
215
216 for (int i = comments.Count - 1; i > -1; i--)
217 {
218 var currentComment = comments[i];
219 if (currentComment.User?.Id == userId && (currentComment.Body?.StartsWith(header) ?? false))
220 {
221 if (currentComment.Body.Length > 250000)
222 { // Limit should be 262,143 so we'll leave a 12,143 buffer
223 return null;
224 }
225
226 return currentComment;
227 }
228 }
229
230 return null;
231 }
232
234 public async ValueTask<long> GetRepositoryId(string repoOwner, string repoName, CancellationToken cancellationToken)
235 {
236 ArgumentNullException.ThrowIfNull(repoOwner);
237
238 ArgumentNullException.ThrowIfNull(repoName);
239
240 logger.LogTrace("GetRepositoryId");
241
242 var repo = await gitHubClient
243 .Repository
244 .Get(
245 repoOwner,
246 repoName)
247 .WaitAsync(cancellationToken);
248
249 return repo.Id;
250 }
251
253 public async ValueTask<long> CreateDeployment(NewDeployment newDeployment, string repoOwner, string repoName, CancellationToken cancellationToken)
254 {
255 ArgumentNullException.ThrowIfNull(newDeployment);
256
257 ArgumentNullException.ThrowIfNull(repoOwner);
258
259 ArgumentNullException.ThrowIfNull(repoName);
260
261 logger.LogTrace("CreateDeployment");
262
263 var deployment = await gitHubClient
264 .Repository
265 .Deployment
266 .Create(
267 repoOwner,
268 repoName,
269 newDeployment)
270 .WaitAsync(cancellationToken);
271
272 return deployment.Id;
273 }
274
276 public Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, string repoOwner, string repoName, long deploymentId, CancellationToken cancellationToken)
277 {
278 ArgumentNullException.ThrowIfNull(newDeploymentStatus);
279
280 ArgumentNullException.ThrowIfNull(repoOwner);
281
282 ArgumentNullException.ThrowIfNull(repoName);
283
284 logger.LogTrace("CreateDeploymentStatus");
285 return gitHubClient
286 .Repository
287 .Deployment
288 .Status
289 .Create(
290 repoOwner,
291 repoName,
292 deploymentId,
293 newDeploymentStatus)
294 .WaitAsync(cancellationToken);
295 }
296
298 public Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, long repoId, long deploymentId, CancellationToken cancellationToken)
299 {
300 ArgumentNullException.ThrowIfNull(newDeploymentStatus);
301
302 logger.LogTrace("CreateDeploymentStatus");
303 return gitHubClient
304 .Repository
305 .Deployment
306 .Status
307 .Create(
308 repoId,
309 deploymentId,
310 newDeploymentStatus)
311 .WaitAsync(cancellationToken);
312 }
313
315 public Task<PullRequest> GetPullRequest(string repoOwner, string repoName, int pullRequestNumber, CancellationToken cancellationToken)
316 {
317 ArgumentNullException.ThrowIfNull(repoOwner);
318
319 ArgumentNullException.ThrowIfNull(repoName);
320
321 logger.LogTrace("GetPullRequest");
322 return gitHubClient
323 .Repository
324 .PullRequest
325 .Get(
326 repoOwner,
327 repoName,
328 pullRequestNumber)
329 .WaitAsync(cancellationToken);
330 }
331
333 public Task<GitHubCommit> GetCommit(string repoOwner, string repoName, string committish, CancellationToken cancellationToken)
334 {
335 ArgumentNullException.ThrowIfNull(repoOwner);
336
337 ArgumentNullException.ThrowIfNull(repoName);
338
339 logger.LogTrace("GetPulGetCommitlRequest");
340 return gitHubClient
341 .Repository
342 .Commit
343 .Get(
344 repoOwner,
345 repoName,
346 committish)
347 .WaitAsync(cancellationToken);
348 }
349 }
350}
Configuration for the automatic update system.
long GitHubRepositoryId
The Octokit.Repository.Id of the tgstation-server fork to receive updates from.
Service for interacting with the GitHub API. Authenticated or otherwise.
async ValueTask< long > CreateDeployment(NewDeployment newDeployment, string repoOwner, string repoName, CancellationToken cancellationToken)
Create a newDeployment on a target repostiory.A ValueTask<TResult> resulting in the new deployment's...
Task< PullRequest > GetPullRequest(string repoOwner, string repoName, int pullRequestNumber, CancellationToken cancellationToken)
Get a given pullRequestNumber .A Task<TResult> resulting in the target PullRequest.
readonly UpdatesConfiguration updatesConfiguration
The UpdatesConfiguration for the GitHubService.
GitHubService(IGitHubClient gitHubClient, ILogger< GitHubService > logger, UpdatesConfiguration updatesConfiguration)
Initializes a new instance of the GitHubService class.
async ValueTask< IssueComment?> GetExistingCommentOnIssue(string repoOwner, string repoName, string header, int issueNumber, CancellationToken cancellationToken)
Gets an IssueComment for a particular issueNumber with the provided header if it exists and is not ...
readonly ILogger< GitHubService > logger
The ILogger for the GitHubService.
async ValueTask< Uri > GetUpdatesRepositoryUrl(CancellationToken cancellationToken)
Gets the Uri of the repository designated as the updates repository.A ValueTask<TResult> resulting in...
Task< GitHubCommit > GetCommit(string repoOwner, string repoName, string committish, CancellationToken cancellationToken)
Get a given committish .A Task<TResult> resulting in the target GitHubCommit.
Task AppendCommentOnIssue(string repoOwner, string repoName, string comment, IssueComment issueComment, CancellationToken cancellationToken)
Append a comment on an existing issueComment .A Task representing the running operation.
async ValueTask< long > GetRepositoryId(string repoOwner, string repoName, CancellationToken cancellationToken)
Get a target repostiory's ID.A ValueTask<TResult> resulting in the target repository's ID.
async ValueTask< string > CreateOAuthAccessToken(OAuthConfiguration oAuthConfiguration, string code, CancellationToken cancellationToken)
Attempt to get an OAuth token from a given code .A ValueTask<TResult> resulting in a string represent...
Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, long repoId, long deploymentId, CancellationToken cancellationToken)
Create a newDeploymentStatus on a target deployment.A Task representing the running operation.
Task CommentOnIssue(string repoOwner, string repoName, string comment, int issueNumber, CancellationToken cancellationToken)
Create a comment on a given issueNumber .A Task representing the running operation.
Task CreateDeploymentStatus(NewDeploymentStatus newDeploymentStatus, string repoOwner, string repoName, long deploymentId, CancellationToken cancellationToken)
Create a newDeploymentStatus on a target deployment.A Task representing the running operation.
async ValueTask< Dictionary< Version, Release > > GetTgsReleases(CancellationToken cancellationToken)
Get all valid TGS Releases from the configured update source.A ValueTask<TResult> resulting in a Dict...
async ValueTask< long > GetCurrentUserId(CancellationToken cancellationToken)
Get the current user's ID.A ValueTask<TResult> resulting in the current user's ID.
readonly IGitHubClient gitHubClient
The IGitHubClient for the GitHubService.
IGitHubService that exposes functions that require authentication.