tgstation-server 6.17.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
Public Member Functions | Private Member Functions | Private Attributes | Static Private Attributes | List of all members
Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory Class Referencesealed

More...

Inheritance diagram for Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory:
Inheritance graph
[legend]
Collaboration diagram for Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory:
Collaboration graph
[legend]

Public Member Functions

 GitHubClientFactory (IAssemblyInformationProvider assemblyInformationProvider, IHttpMessageHandlerFactory httpMessageHandlerFactory, ILogger< GitHubClientFactory > logger, IOptions< GeneralConfiguration > generalConfigurationOptions)
 Initializes a new instance of the GitHubClientFactory class.
 
void Dispose ()
 
async ValueTask< IGitHubClient > CreateClient (CancellationToken cancellationToken)
 Create a IGitHubClient client. Low rate limit unless the server's GitHubAccessToken is set to bypass it.
Parameters
cancellationTokenThe CancellationToken for the operation.
Returns
A new IGitHubClient.

 
async ValueTask< IGitHubClient > CreateClient (string accessToken, CancellationToken cancellationToken)
 Create a client with authentication using a personal access token.
Parameters
accessTokenThe GitHub personal access token.
cancellationTokenThe CancellationToken for the operation.
Returns
A new IGitHubClient.

 
ValueTask< IGitHubClient?> CreateClientForRepository (string accessString, RepositoryIdentifier repositoryIdentifier, CancellationToken cancellationToken)
 Creates a GitHub client that will only be used for a given repositoryIdentifier .
Parameters
accessStringThe GitHub personal access token or TGS encoded app private key string.
repositoryIdentifierThe RepositoryIdentifier for the GitHub ID that the client will be used to connect to.
cancellationTokenThe CancellationToken for the operation.
Returns
A ValueTask<TResult> resulting in a new IGitHubClient for the given repositoryIdentifier or null if authentication failed.

 
IGitHubClient? CreateAppClient (string tgsEncodedAppPrivateKey)
 Create an App (not installation) authenticated IGitHubClient.
Parameters
tgsEncodedAppPrivateKeyThe TGS encoded app private key string.
Returns
A new app auth IGitHubClient for the given tgsEncodedAppPrivateKey on success null on failure.

 

Private Member Functions

async ValueTask< IGitHubClient?> GetOrCreateClient (string? accessString, RepositoryIdentifier? repositoryIdentifier, CancellationToken cancellationToken)
 Retrieve a GitHubClient from the clientCache or add a new one based on a given accessString .
 
GitHubClient? CreateAppClientInternal (string tgsEncodedAppPrivateKey)
 Create an App (not installation) authenticated GitHubClient.
 
GitHubClient CreateUnauthenticatedClient ()
 Creates an unauthenticated GitHubClient.
 

Private Attributes

readonly IAssemblyInformationProvider assemblyInformationProvider
 The IAssemblyInformationProvider for the GitHubClientFactory.
 
readonly IHttpMessageHandlerFactory httpMessageHandlerFactory
 The IHttpMessageHandlerFactory for the GitHubClientFactory.
 
readonly ILogger< GitHubClientFactorylogger
 The ILogger for the GitHubClientFactory.
 
readonly GeneralConfiguration generalConfiguration
 The GeneralConfiguration for the GitHubClientFactory.
 
readonly Dictionary< string,(GitHubClient Client, DateTimeOffset LastUsed, DateTimeOffset? Expiry)> clientCache
 Cache of created GitHubClients and last used/expiry times, keyed by access token.
 
readonly SemaphoreSlim clientCacheSemaphore
 The SemaphoreSlim used to guard access to clientCache.
 

Static Private Attributes

const uint ClientCacheHours = 1
 Limit to the amount of hours a GitHubClient can live in the clientCache.
 
const uint AppTokenExpiryGraceMinutes = 15
 Minutes before tokens expire before not using them.
 
const string DefaultCacheKey = "~!@TGS_DEFAULT_GITHUB_CLIENT_CACHE_KEY@!~"
 The clientCache KeyValuePair<TKey, TValue>.Key used in place of null when accessing a configuration-based client with no token set in GeneralConfiguration.GitHubAccessToken.
 

Detailed Description

Definition at line 25 of file GitHubClientFactory.cs.

Constructor & Destructor Documentation

◆ GitHubClientFactory()

Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GitHubClientFactory ( IAssemblyInformationProvider  assemblyInformationProvider,
IHttpMessageHandlerFactory  httpMessageHandlerFactory,
ILogger< GitHubClientFactory logger,
IOptions< GeneralConfiguration generalConfigurationOptions 
)

Initializes a new instance of the GitHubClientFactory class.

Parameters
assemblyInformationProviderThe value of assemblyInformationProvider.
httpMessageHandlerFactoryThe value of httpMessageHandlerFactory.
loggerThe value of logger.
generalConfigurationOptionsThe IOptions<TOptions> containing the value of generalConfiguration.

Definition at line 80 of file GitHubClientFactory.cs.

85 {
86 this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
87 this.httpMessageHandlerFactory = httpMessageHandlerFactory ?? throw new ArgumentNullException(nameof(httpMessageHandlerFactory));
88 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
89 generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
90
91 clientCache = new Dictionary<string, (GitHubClient, DateTimeOffset, DateTimeOffset?)>();
92 clientCacheSemaphore = new SemaphoreSlim(1, 1);
93 }
readonly IHttpMessageHandlerFactory httpMessageHandlerFactory
The IHttpMessageHandlerFactory for the GitHubClientFactory.
readonly SemaphoreSlim clientCacheSemaphore
The SemaphoreSlim used to guard access to clientCache.
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the GitHubClientFactory.
readonly ILogger< GitHubClientFactory > logger
The ILogger for the GitHubClientFactory.
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the GitHubClientFactory.
readonly Dictionary< string,(GitHubClient Client, DateTimeOffset LastUsed, DateTimeOffset? Expiry)> clientCache
Cache of created GitHubClients and last used/expiry times, keyed by access token.

References Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.assemblyInformationProvider, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.clientCache, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.clientCacheSemaphore, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.generalConfiguration, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.httpMessageHandlerFactory, and Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.logger.

Member Function Documentation

◆ CreateAppClient()

IGitHubClient? Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateAppClient ( string  tgsEncodedAppPrivateKey)

Create an App (not installation) authenticated IGitHubClient.

Parameters
tgsEncodedAppPrivateKeyThe TGS encoded app private key string.
Returns
A new app auth IGitHubClient for the given tgsEncodedAppPrivateKey on success null on failure.

Implements Tgstation.Server.Host.Utils.GitHub.IGitHubClientFactory.

◆ CreateAppClientInternal()

GitHubClient? Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateAppClientInternal ( string  tgsEncodedAppPrivateKey)
private

Create an App (not installation) authenticated GitHubClient.

Parameters
tgsEncodedAppPrivateKeyThe TGS encoded app private key string.
Returns
A new app auth GitHubClient for the given tgsEncodedAppPrivateKey on success null on failure.

Definition at line 270 of file GitHubClientFactory.cs.

271 {
272 var client = CreateUnauthenticatedClient();
273 var splits = tgsEncodedAppPrivateKey.Split(':');
274 if (splits.Length != 2)
275 {
276 logger.LogError("Failed to parse serialized Client ID & PEM! Expected 2 chunks, got {chunkCount}", splits.Length);
277 return null;
278 }
279
280 byte[] pemBytes;
281 try
282 {
283 pemBytes = Convert.FromBase64String(splits[1]);
284 }
285 catch (Exception ex)
286 {
287 logger.LogError(ex, "Failed to parse supposed base64 PEM!");
288 return null;
289 }
290
291 var pem = Encoding.UTF8.GetString(pemBytes);
292
293 using var rsa = RSA.Create();
294
295 try
296 {
297 rsa.ImportFromPem(pem);
298 }
299 catch (Exception ex)
300 {
301 logger.LogWarning(ex, "Failed to parse PEM!");
302 return null;
303 }
304
305 var signingCredentials = new SigningCredentials(
306 new RsaSecurityKey(rsa),
307 SecurityAlgorithms.RsaSha256)
308 {
309 // https://stackoverflow.com/questions/62307933/rsa-disposed-object-error-every-other-test
310 CryptoProviderFactory = new CryptoProviderFactory
311 {
312 CacheSignatureProviders = false,
313 },
314 };
315 var jwtSecurityTokenHandler = new JwtSecurityTokenHandler { SetDefaultTimesOnTokenCreation = false };
316
317 var nowDateTime = DateTime.UtcNow;
318
319 var appOrClientId = splits[0][RepositorySettings.TgsAppPrivateKeyPrefix.Length..];
320
321 var jwt = jwtSecurityTokenHandler.CreateToken(new SecurityTokenDescriptor
322 {
323 Issuer = appOrClientId,
324 Expires = nowDateTime.AddMinutes(10),
325 IssuedAt = nowDateTime,
326 SigningCredentials = signingCredentials,
327 });
328
329 var jwtStr = jwtSecurityTokenHandler.WriteToken(jwt);
330 client.Credentials = new Credentials(jwtStr, AuthenticationType.Bearer);
331 return client;
332 }
Represents configurable settings for a git repository.
const string TgsAppPrivateKeyPrefix
Prefix for TGS encoded app private keys. This is encoded in the format PREFIX + (APP_ID OR CLIENT_ID)...
GitHubClient CreateUnauthenticatedClient()
Creates an unauthenticated GitHubClient.

References Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateUnauthenticatedClient(), Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.logger, and Tgstation.Server.Api.Models.RepositorySettings.TgsAppPrivateKeyPrefix.

Referenced by Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GetOrCreateClient().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ CreateClient() [1/2]

async ValueTask< IGitHubClient > Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateClient ( CancellationToken  cancellationToken)

Create a IGitHubClient client. Low rate limit unless the server's GitHubAccessToken is set to bypass it.

Parameters
cancellationTokenThe CancellationToken for the operation.
Returns
A new IGitHubClient.

Implements Tgstation.Server.Host.Utils.GitHub.IGitHubClientFactory.

◆ CreateClient() [2/2]

async ValueTask< IGitHubClient > Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateClient ( string  accessToken,
CancellationToken  cancellationToken 
)

Create a client with authentication using a personal access token.

Parameters
accessTokenThe GitHub personal access token.
cancellationTokenThe CancellationToken for the operation.
Returns
A new IGitHubClient.

Implements Tgstation.Server.Host.Utils.GitHub.IGitHubClientFactory.

◆ CreateClientForRepository()

ValueTask< IGitHubClient?> Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateClientForRepository ( string  accessString,
RepositoryIdentifier  repositoryIdentifier,
CancellationToken  cancellationToken 
)

Creates a GitHub client that will only be used for a given repositoryIdentifier .

Parameters
accessStringThe GitHub personal access token or TGS encoded app private key string.
repositoryIdentifierThe RepositoryIdentifier for the GitHub ID that the client will be used to connect to.
cancellationTokenThe CancellationToken for the operation.
Returns
A ValueTask<TResult> resulting in a new IGitHubClient for the given repositoryIdentifier or null if authentication failed.

Implements Tgstation.Server.Host.Utils.GitHub.IGitHubClientFactory.

◆ CreateUnauthenticatedClient()

GitHubClient Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateUnauthenticatedClient ( )
private

Creates an unauthenticated GitHubClient.

Returns
A new GitHubClient.

Definition at line 338 of file GitHubClientFactory.cs.

339 {
341#pragma warning disable CA2000 // Dispose objects before losing scope
342 var handler = httpMessageHandlerFactory.CreateHandler();
343 try
344 {
345 var clientAdapter = new HttpClientAdapter(() => handler);
346#pragma warning restore CA2000 // Dispose objects before losing scope
347 handler = null;
348 try
349 {
350 return new GitHubClient(
351 new Connection(
352 new ProductHeaderValue(
353 product.Name,
354 product.Version),
355 clientAdapter));
356 }
357 catch
358 {
359 clientAdapter.Dispose();
360 throw;
361 }
362 }
363 catch
364 {
365 handler?.Dispose();
366 throw;
367 }
368 }
ProductInfoHeaderValue ProductInfoHeaderValue
The ProductInfoHeaderValue for the assembly.

References Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.assemblyInformationProvider, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.httpMessageHandlerFactory, and Tgstation.Server.Host.System.IAssemblyInformationProvider.ProductInfoHeaderValue.

Referenced by Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateAppClientInternal(), and Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GetOrCreateClient().

Here is the caller graph for this function:

◆ Dispose()

void Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.Dispose ( )

◆ GetOrCreateClient()

async ValueTask< IGitHubClient?> Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GetOrCreateClient ( string?  accessString,
RepositoryIdentifier repositoryIdentifier,
CancellationToken  cancellationToken 
)
private

Retrieve a GitHubClient from the clientCache or add a new one based on a given accessString .

Parameters
accessStringOptional access token to use as credentials or GitHub App private key. If using a TGS encoded app private key, repositoryIdentifier must be set.
repositoryIdentifierThe optional RepositoryIdentifier for the GitHub ID that the client will be used to connect to. Must be set if accessString is a TGS encoded app private key.
cancellationTokenThe CancellationToken for the operation.
Returns
A ValueTask<TResult> resulting in the GitHubClient for the given accessString or null if authentication failed.

Definition at line 128 of file GitHubClientFactory.cs.

130 {
131 GitHubClient? client;
132 bool cacheHit;
133 DateTimeOffset? lastUsed;
134 using (await SemaphoreSlimContext.Lock(clientCacheSemaphore, cancellationToken))
135 {
136 string cacheKey;
137 if (String.IsNullOrWhiteSpace(accessString))
138 {
139 accessString = null;
140 cacheKey = DefaultCacheKey;
141 }
142 else
143 cacheKey = accessString;
144
145 var now = DateTimeOffset.UtcNow;
146 cacheHit = clientCache.TryGetValue(cacheKey, out var tuple);
147 var tokenValid = cacheHit && (!tuple.Expiry.HasValue || tuple.Expiry.Value <= now);
148 if (!tokenValid)
149 {
150 if (cacheHit)
151 {
152 logger.LogDebug("Previously cached GitHub token has expired!");
153 clientCache.Remove(cacheKey);
154 }
155
156 logger.LogTrace("Creating new GitHubClient...");
157
158 DateTimeOffset? expiry = null;
159 if (accessString != null)
160 {
161 if (accessString.StartsWith(RepositorySettings.TgsAppPrivateKeyPrefix))
162 {
163 if (repositoryIdentifier == null)
164 throw new InvalidOperationException("Cannot create app installation key without target repositoryIdentifier!");
165
166 logger.LogTrace("Performing GitHub App authentication for installation on repository {installationRepositoryId}", repositoryIdentifier);
167
168 client = CreateAppClientInternal(accessString);
169 if (client == null)
170 return null;
171
172 Installation installation;
173 try
174 {
175 var installationTask = repositoryIdentifier.IsSlug
176 ? client.GitHubApps.GetRepositoryInstallationForCurrent(repositoryIdentifier.Owner, repositoryIdentifier.Name)
177 : client.GitHubApps.GetRepositoryInstallationForCurrent(repositoryIdentifier.RepositoryId.Value);
178 installation = await installationTask;
179 }
180 catch (Exception ex)
181 {
182 logger.LogError(ex, "Failed to perform app authentication!");
183 return null;
184 }
185
186 cancellationToken.ThrowIfCancellationRequested();
187 try
188 {
189 var installToken = await client.GitHubApps.CreateInstallationToken(installation.Id);
190
191 client.Credentials = new Credentials(installToken.Token);
192 expiry = installToken.ExpiresAt.AddMinutes(-AppTokenExpiryGraceMinutes);
193 }
194 catch (Exception ex)
195 {
196 logger.LogError(ex, "Failed to perform installation authentication!");
197 return null;
198 }
199 }
200 else
201 {
203 client.Credentials = new Credentials(accessString);
204 }
205 }
206 else
208
209 clientCache.Add(cacheKey, (Client: client, LastUsed: now, Expiry: expiry));
210 lastUsed = null;
211 }
212 else
213 {
214 logger.LogTrace("Cache hit for GitHubClient");
215 client = tuple.Client;
216 lastUsed = tuple.LastUsed;
217 tuple.LastUsed = now;
218 }
219
220 // Prune the cache
221 var purgeCount = 0U;
222 var purgeAfter = now.AddHours(-ClientCacheHours);
223 foreach (var key in clientCache.Keys.ToList())
224 {
225 if (key == cacheKey)
226 continue; // save the hash lookup
227
228 tuple = clientCache[key];
229 if (tuple.LastUsed <= purgeAfter || (tuple.Expiry.HasValue && tuple.Expiry.Value <= now))
230 {
231 clientCache.Remove(key);
232 ++purgeCount;
233 }
234 }
235
236 if (purgeCount > 0)
237 logger.LogDebug(
238 "Pruned {count} expired GitHub client(s) from cache that haven't been used in {purgeAfterHours} hours.",
239 purgeCount,
241 }
242
243 var rateLimitInfo = client.GetLastApiInfo()?.RateLimit;
244 if (rateLimitInfo != null)
245 if (rateLimitInfo.Remaining == 0)
246 logger.LogWarning(
247 "Requested GitHub client has no requests remaining! Limit resets at {resetTime}",
248 rateLimitInfo.Reset.ToString("o"));
249 else if (rateLimitInfo.Remaining < 25) // good luck hitting these lines on codecov
250 logger.LogWarning(
251 "Requested GitHub client has only {remainingRequests} requests remaining after the usage at {lastUse}! Limit resets at {resetTime}",
252 rateLimitInfo.Remaining,
253 lastUsed,
254 rateLimitInfo.Reset.ToString("o"));
255 else
256 logger.LogDebug(
257 "Requested GitHub client has {remainingRequests} requests remaining after the usage at {lastUse}. Limit resets at {resetTime}",
258 rateLimitInfo.Remaining,
259 lastUsed,
260 rateLimitInfo.Reset.ToString("o"));
261
262 return client;
263 }
const string DefaultCacheKey
The clientCache KeyValuePair<TKey, TValue>.Key used in place of null when accessing a configuration-b...
GitHubClient? CreateAppClientInternal(string tgsEncodedAppPrivateKey)
Create an App (not installation) authenticated GitHubClient.
const uint AppTokenExpiryGraceMinutes
Minutes before tokens expire before not using them.
const uint ClientCacheHours
Limit to the amount of hours a GitHubClient can live in the clientCache.

References Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.AppTokenExpiryGraceMinutes, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.clientCache, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.ClientCacheHours, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.clientCacheSemaphore, Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateAppClientInternal(), Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.CreateUnauthenticatedClient(), Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.DefaultCacheKey, Tgstation.Server.Host.Utils.SemaphoreSlimContext.Lock(), Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.logger, Tgstation.Server.Host.Utils.GitHub.RepositoryIdentifier.Name, Tgstation.Server.Host.Utils.GitHub.RepositoryIdentifier.Owner, Tgstation.Server.Host.Utils.GitHub.RepositoryIdentifier.RepositoryId, and Tgstation.Server.Api.Models.RepositorySettings.TgsAppPrivateKeyPrefix.

Here is the call graph for this function:

Member Data Documentation

◆ AppTokenExpiryGraceMinutes

const uint Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.AppTokenExpiryGraceMinutes = 15
staticprivate

Minutes before tokens expire before not using them.

Definition at line 36 of file GitHubClientFactory.cs.

Referenced by Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GetOrCreateClient().

◆ assemblyInformationProvider

readonly IAssemblyInformationProvider Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.assemblyInformationProvider
private

◆ clientCache

readonly Dictionary<string, (GitHubClient Client, DateTimeOffset LastUsed, DateTimeOffset? Expiry)> Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.clientCache
private

Cache of created GitHubClients and last used/expiry times, keyed by access token.

Definition at line 66 of file GitHubClientFactory.cs.

Referenced by Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GetOrCreateClient(), and Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GitHubClientFactory().

◆ ClientCacheHours

const uint Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.ClientCacheHours = 1
staticprivate

Limit to the amount of hours a GitHubClient can live in the clientCache.

Set to app installation token lifetime, which is the lowest. See https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app.

Definition at line 31 of file GitHubClientFactory.cs.

Referenced by Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GetOrCreateClient().

◆ clientCacheSemaphore

readonly SemaphoreSlim Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.clientCacheSemaphore
private

◆ DefaultCacheKey

const string Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.DefaultCacheKey = "~!@TGS_DEFAULT_GITHUB_CLIENT_CACHE_KEY@!~"
staticprivate

The clientCache KeyValuePair<TKey, TValue>.Key used in place of null when accessing a configuration-based client with no token set in GeneralConfiguration.GitHubAccessToken.

Definition at line 41 of file GitHubClientFactory.cs.

Referenced by Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GetOrCreateClient().

◆ generalConfiguration

readonly GeneralConfiguration Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.generalConfiguration
private

The GeneralConfiguration for the GitHubClientFactory.

Definition at line 61 of file GitHubClientFactory.cs.

Referenced by Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.GitHubClientFactory().

◆ httpMessageHandlerFactory

readonly IHttpMessageHandlerFactory Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.httpMessageHandlerFactory
private

◆ logger

readonly ILogger<GitHubClientFactory> Tgstation.Server.Host.Utils.GitHub.GitHubClientFactory.logger
private

The documentation for this class was generated from the following file: