Skip to content

Commit 83a5df3

Browse files
committed
Add storage to Aspire
1 parent 95561a5 commit 83a5df3

File tree

8 files changed

+153
-8
lines changed

8 files changed

+153
-8
lines changed

src/Exceptionless.AppHost/Exceptionless.AppHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PackageReference Include="Aspire.Hosting.NodeJs" Version="9.0.0" />
1717
<PackageReference Include="Aspire.Hosting.Redis" Version="9.0.0" />
1818
<PackageReference Include="AspNetCore.HealthChecks.Elasticsearch" Version="8.0.1" />
19+
<PackageReference Include="Foundatio.AWS" Version="11.0.6" />
1920
</ItemGroup>
2021

2122
<ItemGroup>
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using Foundatio.Storage;
2+
3+
namespace Aspire.Hosting;
4+
5+
public static class MinIoExtensions
6+
{
7+
public static IResourceBuilder<MinIoResource> AddMinIo(
8+
this IDistributedApplicationBuilder builder,
9+
string name,
10+
Action<MinIoBuilder>? configure = null)
11+
{
12+
var options = new MinIoBuilder();
13+
configure?.Invoke(options);
14+
15+
var resource = new MinIoResource(name, options.AccessKey, options.SecretKey, options.Bucket ?? "storage");
16+
17+
string? connectionString = null;
18+
19+
builder.Eventing.Subscribe<ResourceReadyEvent>(resource, async (@event, ct) =>
20+
{
21+
connectionString = await resource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false);
22+
23+
if (connectionString == null)
24+
throw new DistributedApplicationException($"ResourceReadyEvent was published for the '{resource.Name}' resource but the connection string was null.");
25+
26+
var storage = new S3FileStorage(o => o.ConnectionString(connectionString));
27+
try
28+
{
29+
storage.Client.PutBucketAsync(options.Bucket ?? "storage", ct).GetAwaiter().GetResult();
30+
}
31+
catch
32+
{
33+
// ignored
34+
}
35+
});
36+
37+
return builder.AddResource(resource)
38+
.WithImage(MinIoContainerImageTags.Image)
39+
.WithImageRegistry(MinIoContainerImageTags.Registry)
40+
.WithImageTag(MinIoContainerImageTags.Tag)
41+
.WithArgs("server", "/data", "--console-address", $":{MinIoResource.DefaultConsolePort}")
42+
.WithEndpoint(port: options.ApiPort, targetPort: MinIoResource.DefaultApiPort, name: MinIoResource.ApiEndpointName)
43+
.WithHttpEndpoint(port: options.ConsolePort, targetPort: MinIoResource.DefaultConsolePort, name: MinIoResource.ConsoleEndpointName)
44+
.ConfigureCredentials(options)
45+
.ConfigureVolume(options);
46+
}
47+
48+
private static IResourceBuilder<MinIoResource> ConfigureCredentials(
49+
this IResourceBuilder<MinIoResource> builder,
50+
MinIoBuilder options)
51+
{
52+
return builder
53+
.WithEnvironment("MINIO_ROOT_USER", options.AccessKey ?? "minioadmin")
54+
.WithEnvironment("MINIO_ROOT_PASSWORD", options.SecretKey ?? "minioadmin");
55+
}
56+
57+
private static IResourceBuilder<MinIoResource> ConfigureVolume(
58+
this IResourceBuilder<MinIoResource> builder,
59+
MinIoBuilder options)
60+
{
61+
if (!string.IsNullOrEmpty(options.DataVolumePath))
62+
builder = builder.WithVolume(options.DataVolumePath, "/data");
63+
64+
return builder;
65+
}
66+
}
67+
68+
public class MinIoResource(string name, string? accessKey = null, string? secretKey = null, string? bucket = "storage")
69+
: ContainerResource(name), IResourceWithConnectionString
70+
{
71+
internal const string ApiEndpointName = "api";
72+
internal const string ConsoleEndpointName = "console";
73+
internal const int DefaultApiPort = 9000;
74+
internal const int DefaultConsolePort = 9001;
75+
76+
private EndpointReference? _apiReference;
77+
private EndpointReference? _consoleReference;
78+
79+
private EndpointReference ApiEndpoint =>
80+
_apiReference ??= new EndpointReference(this, ApiEndpointName);
81+
82+
private EndpointReference ConsoleEndpoint =>
83+
_consoleReference ??= new EndpointReference(this, ConsoleEndpointName);
84+
85+
public ReferenceExpression ConnectionStringExpression =>
86+
ReferenceExpression.Create(
87+
$"ServiceUrl=http://{ApiEndpoint.Property(EndpointProperty.Host)}:{ApiEndpoint.Property(EndpointProperty.Port)};" +
88+
$"AccessKey={AccessKey ?? "minioadmin"};" +
89+
$"SecretKey={SecretKey ?? "minioadmin"};" +
90+
$"Bucket={Bucket}");
91+
92+
public string? AccessKey { get; } = accessKey;
93+
public string? SecretKey { get; } = secretKey;
94+
public string? Bucket { get; } = bucket;
95+
}
96+
97+
public class MinIoBuilder
98+
{
99+
public int? ApiPort { get; set; }
100+
public int? ConsolePort { get; set; }
101+
public string? AccessKey { get; set; }
102+
public string? SecretKey { get; set; }
103+
public string? Bucket { get; set; }
104+
public string? DataVolumePath { get; set; }
105+
106+
public MinIoBuilder WithPorts(int? apiPort = null, int? consolePort = null)
107+
{
108+
ApiPort = apiPort;
109+
ConsolePort = consolePort;
110+
return this;
111+
}
112+
113+
public MinIoBuilder WithCredentials(string accessKey, string secretKey)
114+
{
115+
AccessKey = accessKey;
116+
SecretKey = secretKey;
117+
return this;
118+
}
119+
120+
public MinIoBuilder WithBucket(string bucket)
121+
{
122+
Bucket = bucket;
123+
return this;
124+
}
125+
126+
public MinIoBuilder WithDataVolume(string path)
127+
{
128+
DataVolumePath = path;
129+
return this;
130+
}
131+
}
132+
133+
internal static class MinIoContainerImageTags
134+
{
135+
internal const string Registry = "docker.io";
136+
internal const string Image = "minio/minio";
137+
internal const string Tag = "latest";
138+
}

src/Exceptionless.AppHost/Program.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
.WithDataVolume("exceptionless.data.v1")
88
.WithKibana(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-Kibana"));
99

10+
var storage = builder.AddMinIo("Storage", s => s.WithCredentials("guest", "password").WithPorts(9000))
11+
.WithLifetime(ContainerLifetime.Persistent)
12+
.WithContainerName("Exceptionless-Storage");
13+
1014
var cache = builder.AddRedis("Redis", port: 6379)
1115
.WithImageTag("7.4")
1216
.WithLifetime(ContainerLifetime.Persistent)
@@ -32,6 +36,7 @@
3236
var api = builder.AddProject<Projects.Exceptionless_Web>("Api", "Exceptionless")
3337
.WithReference(cache)
3438
.WithReference(elastic)
39+
.WithReference(storage)
3540
.WithEnvironment("ConnectionStrings:Email", "smtp://localhost:1025")
3641
.WithEnvironment("RunJobsInProcess", "false")
3742
.WaitFor(elastic)

src/Exceptionless.Job/Exceptionless.Job.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />
2020
<PackageReference Include="OpenTelemetry.Instrumentation.StackExchangeRedis" Version="1.9.0-beta.1" />
2121
<PackageReference Include="OpenTelemetry.Instrumentation.ElasticsearchClient" Version="1.0.0-beta.5" />
22-
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
22+
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.10.0" />
2323
<PackageReference Include="OpenTelemetry.Instrumentation.Process" Version="0.5.0-beta.7" />
2424
</ItemGroup>
2525

src/Exceptionless.Job/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,13 @@ public static IHostBuilder CreateHostBuilder(string[] args)
8585
app.UseSerilogRequestLogging(o =>
8686
{
8787
o.MessageTemplate = "TraceId={TraceId} HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
88-
o.GetLevel = (context, duration, ex) =>
88+
o.GetLevel = new Func<HttpContext, double, Exception?, LogEventLevel>((context, duration, ex) =>
8989
{
9090
if (ex is not null || context.Response.StatusCode > 499)
9191
return LogEventLevel.Error;
9292

9393
return duration < 1000 && context.Response.StatusCode < 400 ? LogEventLevel.Debug : LogEventLevel.Information;
94-
};
94+
});
9595
});
9696

9797
Bootstrapper.LogConfiguration(app.ApplicationServices, options, app.ApplicationServices.GetRequiredService<ILogger<Program>>());

src/Exceptionless.Web/ApmExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public static IHostBuilder AddApm(this IHostBuilder builder, ApmConfig config)
3636

3737
b.AddAspNetCoreInstrumentation(o =>
3838
{
39-
o.Filter = context =>
39+
o.Filter = new Func<HttpContext, bool>(context =>
4040
{
4141
if (context.Request.Path.StartsWithSegments("/api/v2/push", StringComparison.OrdinalIgnoreCase))
4242
return false;
@@ -48,7 +48,7 @@ public static IHostBuilder AddApm(this IHostBuilder builder, ApmConfig config)
4848
return false;
4949

5050
return true;
51-
};
51+
});
5252
});
5353

5454
b.AddElasticsearchClientInstrumentation(c =>

src/Exceptionless.Web/Exceptionless.Web.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />
3030
<PackageReference Include="OpenTelemetry.Instrumentation.StackExchangeRedis" Version="1.9.0-beta.1" />
3131
<PackageReference Include="OpenTelemetry.Instrumentation.ElasticsearchClient" Version="1.0.0-beta.5" />
32-
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.9.0" />
32+
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.10.0" />
3333
<PackageReference Include="OpenTelemetry.Instrumentation.Process" Version="0.5.0-beta.7" />
3434
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="7.1.0" />
3535
<PackageReference Include="Unchase.Swashbuckle.AspNetCore.Extensions" Version="2.7.1" />

src/Exceptionless.Web/Startup.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Foundatio.Extensions.Hosting.Startup;
1515
using Foundatio.Repositories.Exceptions;
1616
using Joonasw.AspNetCore.SecurityHeaders;
17+
using Joonasw.AspNetCore.SecurityHeaders.Csp;
1718
using Microsoft.AspNetCore.Authorization;
1819
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
1920
using Microsoft.AspNetCore.Hosting.Server.Features;
@@ -299,11 +300,11 @@ ApplicationException applicationException when applicationException.Message.Cont
299300
.To("https://api-iam.intercom.io/")
300301
.To("wss://nexus-websocket-a.intercom.io");
301302

302-
csp.OnSendingHeader = context =>
303+
csp.OnSendingHeader = new Func<CspSendingHeaderContext, Task>(context =>
303304
{
304305
context.ShouldNotSend = context.HttpContext.Request.Path.StartsWithSegments("/api");
305306
return Task.CompletedTask;
306-
};
307+
});
307308
});
308309

309310
app.UseSerilogRequestLogging(o =>

0 commit comments

Comments
 (0)