-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSecAgentPluginBootstrapHostedService.cs
More file actions
101 lines (88 loc) · 3.92 KB
/
Copy pathSecAgentPluginBootstrapHostedService.cs
File metadata and controls
101 lines (88 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace SecRandom.SecAgentPlugin;
/// <summary>
/// Best-effort installation of the SecRandom connector in a running SecAgent host.
/// </summary>
public sealed class SecAgentPluginBootstrapHostedService(
ILogger<SecAgentPluginBootstrapHostedService> logger) : BackgroundService
{
private const string ConnectorPluginId = "secrandom";
private const string ConnectorPluginVersion = "0.1.4";
private static readonly Uri BaseUri = new("http://127.0.0.1:42189/");
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try { await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken).ConfigureAwait(false); }
catch (OperationCanceledException) { return; }
while (!stoppingToken.IsCancellationRequested)
{
try
{
await EnsurePluginAsync(stoppingToken).ConfigureAwait(false);
return;
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
return;
}
catch (HttpRequestException)
{
// SecAgent is optional and may simply not be installed or running.
}
catch (Exception ex)
{
logger.LogDebug(ex, "SecAgent connector bootstrap was skipped.");
return;
}
try { await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken).ConfigureAwait(false); }
catch (OperationCanceledException) { return; }
}
}
private async Task EnsurePluginAsync(CancellationToken cancellationToken)
{
using var client = new HttpClient
{
BaseAddress = BaseUri,
Timeout = TimeSpan.FromSeconds(2)
};
using var health = await client.GetAsync("health", cancellationToken).ConfigureAwait(false);
if (!health.IsSuccessStatusCode) return;
var installed = await client.GetFromJsonAsync<PluginListResponse>("plugins", JsonOptions, cancellationToken).ConfigureAwait(false);
var current = installed?.Plugins?.FirstOrDefault(plugin =>
string.Equals(plugin.Id, ConnectorPluginId, StringComparison.OrdinalIgnoreCase));
if (current is not null && !IsOlderVersion(current.Version, ConnectorPluginVersion))
return;
var request = current is null
? new { pluginId = ConnectorPluginId, version = (string?)null }
: new { pluginId = ConnectorPluginId, version = (string?)ConnectorPluginVersion };
using var response = await client.PostAsJsonAsync("plugins/install", request, JsonOptions, cancellationToken).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
logger.LogInformation("Requested local SecAgent to install/update the SecRandom connector plugin to {Version}.", ConnectorPluginVersion);
else
logger.LogDebug("Local SecAgent declined SecRandom connector installation with HTTP {StatusCode}.", response.StatusCode);
}
private sealed class PluginListResponse
{
public List<PluginInfo>? Plugins { get; init; }
}
private sealed class PluginInfo
{
public string? Id { get; init; }
public string? Version { get; init; }
}
private static bool IsOlderVersion(string? current, string desired)
{
if (Version.TryParse(current, out var currentVersion) && Version.TryParse(desired, out var desiredVersion))
return currentVersion < desiredVersion;
return !string.Equals(current, desired, StringComparison.OrdinalIgnoreCase);
}
}