tgstation-server 6.12.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
VersionReportingService.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading;
5using System.Threading.Tasks;
6
7using Microsoft.Extensions.Hosting;
8using Microsoft.Extensions.Logging;
9using Microsoft.Extensions.Options;
10
11using Octokit;
12
21
23{
28 {
33
38
43
48
52 readonly ILogger<VersionReportingService> logger;
53
58
62 CancellationToken shutdownCancellationToken;
63
78 IOptions<TelemetryConfiguration> telemetryConfigurationOptions,
79 ILogger<VersionReportingService> logger)
80 {
81 this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
82 this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
83 this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
84 this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
85 telemetryConfiguration = telemetryConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(telemetryConfigurationOptions));
86 this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
87 }
88
90 public override Task StopAsync(CancellationToken cancellationToken)
91 {
92 shutdownCancellationToken = cancellationToken;
93 return base.StopAsync(cancellationToken);
94 }
95
97 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
98 {
100 {
101 logger.LogDebug("Version telemetry disabled");
102 return;
103 }
104
106 {
107 logger.LogError("Version reporting repository is misconfigured. Telemetry cannot be sent!");
108 return;
109 }
110
112 if (attribute == null)
113 {
114 logger.LogDebug("TGS build configuration does not allow for version telemetry");
115 return;
116 }
117
118 logger.LogDebug("Starting...");
119
120 try
121 {
122 var telemetryIdDirectory = ioManager.GetPathInLocalDirectory(assemblyInformationProvider);
123 var telemetryIdFile = ioManager.ResolvePath(
125 telemetryIdDirectory,
126 "telemetry.id"));
127
128 Guid telemetryId;
129 if (!await ioManager.FileExists(telemetryIdFile, stoppingToken))
130 {
131 telemetryId = Guid.NewGuid();
132 await ioManager.CreateDirectory(telemetryIdDirectory, stoppingToken);
133 await ioManager.WriteAllBytes(telemetryIdFile, Encoding.UTF8.GetBytes(telemetryId.ToString()), stoppingToken);
134 logger.LogInformation("Generated telemetry ID {telemetryId} and wrote to {file}", telemetryId, telemetryIdFile);
135 }
136 else
137 {
138 var contents = await ioManager.ReadAllBytes(telemetryIdFile, stoppingToken);
139
140 string guidStr;
141 try
142 {
143 guidStr = Encoding.UTF8.GetString(contents);
144 }
145 catch (Exception ex)
146 {
147 logger.LogError(ex, "Cannot decode telemetry ID from installation file ({path}). Telemetry will not be sent!", telemetryIdFile);
148 return;
149 }
150
151 if (!Guid.TryParse(guidStr, out telemetryId))
152 {
153 logger.LogError("Cannot parse telemetry ID from installation file ({path}). Telemetry will not be sent!", telemetryIdFile);
154 return;
155 }
156 }
157
158 try
159 {
160 while (!stoppingToken.IsCancellationRequested)
161 {
162 var nextDelayHours = await TryReportVersion(
163 telemetryId,
164 attribute.SerializedKey,
166 false,
167 stoppingToken)
168 ? 24
169 : 1;
170
171 logger.LogDebug("Next version report in {hours} hours", nextDelayHours);
172 await asyncDelayer.Delay(TimeSpan.FromHours(nextDelayHours), stoppingToken);
173 }
174 }
175 catch (OperationCanceledException ex)
176 {
177 logger.LogTrace(ex, "Inner cancellation");
178 }
179
180 shutdownCancellationToken.ThrowIfCancellationRequested();
181
182 logger.LogDebug("Sending shutdown telemetry");
183 await TryReportVersion(
184 telemetryId,
185 attribute.SerializedKey,
187 true,
189 }
190 catch (OperationCanceledException ex)
191 {
192 logger.LogTrace(ex, "Exiting due to outer cancellation...");
193 }
194 catch (Exception ex)
195 {
196 logger.LogError(ex, "Crashed!");
197 }
198 }
199
209 async ValueTask<bool> TryReportVersion(Guid telemetryId, string serializedPem, long repositoryId, bool shutdown, CancellationToken cancellationToken)
210 {
211 logger.LogDebug("Sending version telemetry...");
212
213 var serverFriendlyName = telemetryConfiguration.ServerFriendlyName;
214 if (String.IsNullOrWhiteSpace(serverFriendlyName))
215 serverFriendlyName = null;
216
217 logger.LogTrace(
218 "Repository ID: {repoId}, Server friendly name: {friendlyName}",
219 repositoryId,
220 serverFriendlyName == null
221 ? "(null)"
222 : $"\"{serverFriendlyName}\"");
223 try
224 {
225 var gitHubClient = await gitHubClientFactory.CreateClientForRepository(
226 serializedPem,
227 new RepositoryIdentifier(repositoryId),
228 cancellationToken);
229
230 if (gitHubClient == null)
231 {
232 logger.LogWarning("Could not create GitHub client to connect to repository ID {repoId}!", repositoryId);
233 return false;
234 }
235
236 // remove this lookup once https://github.com/octokit/octokit.net/pull/2960 is merged and released
237 var repository = await gitHubClient.Repository.Get(repositoryId);
238
239 logger.LogTrace("Repository ID {id} resolved to {owner}/{name}", repositoryId, repository.Owner.Name, repository.Name);
240
241 var inputs = new Dictionary<string, object>
242 {
243 { "telemetry_id", telemetryId.ToString() },
244 { "tgs_semver", assemblyInformationProvider.Version.Semver().ToString() },
245 { "shutdown", shutdown ? "true" : "false" },
246 };
247
248 if (serverFriendlyName != null)
249 inputs.Add("server_friendly_name", serverFriendlyName);
250
251 await gitHubClient.Actions.Workflows.CreateDispatch(
252 repository.Owner.Login,
253 repository.Name,
254 ".github/workflows/tgs_deployments_telemetry.yml",
255 new CreateWorkflowDispatch("main")
256 {
257 Inputs = inputs,
258 });
259
260 logger.LogTrace("Telemetry sent successfully");
261
262 return true;
263 }
264 catch (Exception ex)
265 {
266 logger.LogWarning(ex, "Failed to report version!");
267 return false;
268 }
269 }
270 }
271}
string? ServerFriendlyName
The friendly name used on GitHub deployments for version reporting. If null only the server global::S...
long? VersionReportingRepositoryId
The GitHub repository ID used for version reporting.
bool DisableVersionReporting
If version reporting telemetry is disabled.
Handles TGS version reporting, if enabled.
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the VersionReportingService.
async ValueTask< bool > TryReportVersion(Guid telemetryId, string serializedPem, long repositoryId, bool shutdown, CancellationToken cancellationToken)
Make an attempt to report the current IAssemblyInformationProvider.Version to the configured GitHub r...
readonly IIOManager ioManager
The IIOManager for the VersionReportingService.
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the VersionReportingService.
VersionReportingService(IGitHubClientFactory gitHubClientFactory, IIOManager ioManager, IAsyncDelayer asyncDelayer, IAssemblyInformationProvider assemblyInformationProvider, IOptions< TelemetryConfiguration > telemetryConfigurationOptions, ILogger< VersionReportingService > logger)
Initializes a new instance of the VersionReportingService class.
override async Task ExecuteAsync(CancellationToken stoppingToken)
readonly ILogger< VersionReportingService > logger
The ILogger for the VersionReportingService.
CancellationToken shutdownCancellationToken
The CancellationToken passed to StopAsync(CancellationToken).
override Task StopAsync(CancellationToken cancellationToken)
readonly IGitHubClientFactory gitHubClientFactory
The IGitHubClientFactory for the VersionReportingService.
readonly TelemetryConfiguration telemetryConfiguration
The TelemetryConfiguration for the VersionReportingService.
Attribute for bundling the GitHub App serialized private key used for version telemetry.
static ? TelemetryAppSerializedKeyAttribute Instance
Return the Assembly's instance of the TelemetryAppSerializedKeyAttribute.
Identifies a repository either by its RepositoryId or Owner and Name.
Interface for using filesystems.
Definition IIOManager.cs:13
string ResolvePath()
Retrieve the full path of the current working directory.
ValueTask< byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
Returns all the contents of a file at path as a byte array.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
ValueTask WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content.
Task< bool > FileExists(string path, CancellationToken cancellationToken)
Check that the file at path exists.
ValueTask< IGitHubClient?> CreateClientForRepository(string accessString, RepositoryIdentifier repositoryIdentifier, CancellationToken cancellationToken)
Creates a GitHub client that will only be used for a given repositoryIdentifier .
ValueTask Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan .