Skip to content

Commit

Permalink
Merge pull request #124 from rafaelldi/support-preview-6
Browse files Browse the repository at this point in the history
Update to preview 6
rafaelldi authored Apr 25, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents 421e2a0 + 29de0c5 commit 4073f83
Showing 19 changed files with 184 additions and 130 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,10 @@

## [Unreleased]

### Changed

- Support for .NET Aspire preview 6

## [0.5.0] - 2024-04-12

### Changed
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ pluginGroup = me.rafaelldi.aspire
pluginName = aspire-plugin
pluginRepositoryUrl = https://github.com/rafaelldi/aspire-plugin
# SemVer format -> https://semver.org
pluginVersion = 0.5.0
pluginVersion = 0.6.0

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 241
6 changes: 3 additions & 3 deletions src/dotnet/aspire-session-host/ParentProcessWatchdog.cs
Original file line number Diff line number Diff line change
@@ -4,15 +4,15 @@ namespace AspireSessionHost;

internal static class ParentProcessWatchdog
{
private const string RiderParentProcessPid = "RIDER_PARENT_PROCESS_PID";
private const string RiderParentProcessProcessId = "RIDER_PARENT_PROCESS_ID";

public static void StartNewIfAvailable()
{
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(RiderParentProcessPid)))
if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(RiderParentProcessProcessId)))
{
return;
}

ProcessWatchdog.StartWatchdogForPidEnvironmentVariable(RiderParentProcessPid);
ProcessWatchdog.StartWatchdogForPidEnvironmentVariable(RiderParentProcessProcessId);
}
}
2 changes: 2 additions & 0 deletions src/dotnet/aspire-session-host/Program.cs
Original file line number Diff line number Diff line change
@@ -19,6 +19,8 @@

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEnvironmentVariables("Rider_");

builder.Services.AddGrpc();

var connection = new Connection(rdPort.Value);
Original file line number Diff line number Diff line change
@@ -3,25 +3,35 @@
using Grpc.Core;
using JetBrains.Collections.Viewable;
using JetBrains.Lifetimes;
using Microsoft.Extensions.Options;
using Polly;
using Polly.Registry;

namespace AspireSessionHost.Resources;

internal sealed class SessionResourceLogService(
internal sealed class ResourceLogService(
Connection connection,
DashboardService.DashboardServiceClient client,
ResiliencePipelineProvider<string> resiliencePipelineProvider,
ILogger<SessionResourceLogService> logger
IOptions<ResourceServiceOptions> options,
ILogger<ResourceLogService> logger
) : IDisposable
{
private const string ApiKeyHeader = "x-resource-service-api-key";
private readonly Metadata _headers = [];
private readonly ResourceServiceOptions _optionValue = options.Value;
private readonly LifetimeDefinition _lifetimeDef = new();

private readonly ResiliencePipeline _pipeline =
resiliencePipelineProvider.GetPipeline(nameof(SessionResourceLogService));
resiliencePipelineProvider.GetPipeline(nameof(ResourceLogService));

internal async Task Initialize()
{
if (_optionValue.ApiKey is not null)
{
_headers.Add(ApiKeyHeader, _optionValue.ApiKey);
}

await connection.DoWithModel(model =>
{
model.Resources.View(_lifetimeDef.Lifetime, (lifetime, resourceId, resource) =>
@@ -62,7 +72,7 @@ private async Task<bool> SendWatchResourceLogsRequest(
logger.LogTrace("Sending log watching request for the resource {resourceName}", resourceName);

var request = new WatchResourceConsoleLogsRequest { ResourceName = resourceName };
var response = client.WatchResourceConsoleLogs(request, cancellationToken: ct);
var response = client.WatchResourceConsoleLogs(request, headers: _headers, cancellationToken: ct);
await foreach (var update in response.ResponseStream.ReadAllAsync(ct))
{
foreach (var logLine in update.LogLines)
Original file line number Diff line number Diff line change
@@ -3,25 +3,35 @@
using Grpc.Core;
using JetBrains.Lifetimes;
using JetBrains.Rd.Base;
using Microsoft.Extensions.Options;
using Polly;
using Polly.Registry;

namespace AspireSessionHost.Resources;

internal sealed class SessionResourceService(
internal sealed class ResourceService(
Connection connection,
DashboardService.DashboardServiceClient client,
ResiliencePipelineProvider<string> resiliencePipelineProvider,
ILogger<SessionResourceService> logger
IOptions<ResourceServiceOptions> options,
ILogger<ResourceService> logger
) : IDisposable
{
private const string ApiKeyHeader = "x-resource-service-api-key";
private readonly Metadata _headers = [];
private readonly ResourceServiceOptions _optionValue = options.Value;
private readonly LifetimeDefinition _lifetimeDef = new();

private readonly ResiliencePipeline _pipeline =
resiliencePipelineProvider.GetPipeline(nameof(SessionResourceService));
resiliencePipelineProvider.GetPipeline(nameof(ResourceService));

internal void Initialize()
{
if (_optionValue.ApiKey is not null)
{
_headers.Add(ApiKeyHeader, _optionValue.ApiKey);
}

_lifetimeDef.Lifetime.StartAttachedAsync(TaskScheduler.Default, async () => await WatchResources());
}

@@ -45,7 +55,7 @@ private async Task SendWatchResourcesRequest(
CancellationToken ct)
{
var request = new WatchResourcesRequest { IsReconnect = retryCount > 1 };
var response = client.WatchResources(request, cancellationToken: ct);
var response = client.WatchResources(request, headers: _headers, cancellationToken: ct);
await foreach (var update in response.ResponseStream.ReadAllAsync(ct))
{
switch (update.KindCase)
18 changes: 18 additions & 0 deletions src/dotnet/aspire-session-host/Resources/ResourceServiceOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Microsoft.Extensions.Options;

namespace AspireSessionHost.Resources;

public sealed class ResourceServiceOptions
{
public string? ApiKey { get; set; }
};

internal sealed class ResourceServiceOptionSetup(IConfiguration configuration) : IConfigureOptions<ResourceServiceOptions>
{
private const string SectionName = "ResourceService";

public void Configure(ResourceServiceOptions options)
{
configuration.GetSection(SectionName).Bind(options);
}
}
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@ internal static void AddResourceServices(this IServiceCollection services)
var resourceEndpointUrl = GetResourceEndpointUrl();
if (resourceEndpointUrl is null) return;

services.ConfigureOptions<ResourceServiceOptionSetup>();

var retryPolicy = new MethodConfig
{
Names = { MethodName.Default },
@@ -29,10 +31,10 @@ internal static void AddResourceServices(this IServiceCollection services)
services
.AddGrpcClient<DashboardService.DashboardServiceClient>(o => { o.Address = resourceEndpointUrl; })
.ConfigureChannel(o => { o.ServiceConfig = new ServiceConfig { MethodConfigs = { retryPolicy } }; });
services.AddSingleton<SessionResourceService>();
services.AddSingleton<SessionResourceLogService>();
services.AddSingleton<ResourceService>();
services.AddSingleton<ResourceLogService>();

services.AddResiliencePipeline(nameof(SessionResourceLogService), builder =>
services.AddResiliencePipeline(nameof(ResourceLogService), builder =>
{
builder.AddRetry(new RetryStrategyOptions
{
@@ -43,7 +45,7 @@ internal static void AddResourceServices(this IServiceCollection services)
});
});

services.AddResiliencePipeline(nameof(SessionResourceService), builder =>
services.AddResiliencePipeline(nameof(ResourceService), builder =>
{
builder.AddRetry(new RetryStrategyOptions
{
@@ -57,9 +59,9 @@ internal static void AddResourceServices(this IServiceCollection services)
internal static async Task InitializeResourceServices(this IServiceProvider services)
{
using var scope = services.CreateScope();
var resourceService = scope.ServiceProvider.GetRequiredService<SessionResourceService>();
var resourceService = scope.ServiceProvider.GetRequiredService<ResourceService>();
resourceService.Initialize();
var resourceLogService = scope.ServiceProvider.GetRequiredService<SessionResourceLogService>();
var resourceLogService = scope.ServiceProvider.GetRequiredService<ResourceLogService>();
await resourceLogService.Initialize();
}
}
118 changes: 83 additions & 35 deletions src/main/kotlin/me/rafaelldi/aspire/run/AspireHostExecutorFactory.kt
Original file line number Diff line number Diff line change
@@ -19,23 +19,24 @@ import com.jetbrains.rider.run.environment.ProjectProcessOptions
import com.jetbrains.rider.runtime.DotNetExecutable
import com.jetbrains.rider.runtime.RiderDotNetActiveRuntimeHost
import com.jetbrains.rider.util.NetUtils
import me.rafaelldi.aspire.run.AspireHostProgramRunner.Companion.DEBUG_SESSION_PORT
import me.rafaelldi.aspire.run.AspireHostProgramRunner.Companion.DEBUG_SESSION_TOKEN
import me.rafaelldi.aspire.run.AspireHostProgramRunner.Companion.DOTNET_DASHBOARD_OTLP_ENDPOINT_URL
import me.rafaelldi.aspire.run.AspireHostProgramRunner.Companion.DOTNET_RESOURCE_SERVICE_ENDPOINT_URL
import me.rafaelldi.aspire.util.ASPIRE_ALLOW_UNSECURED_TRANSPORT
import me.rafaelldi.aspire.util.ASPNETCORE_URLS
import me.rafaelldi.aspire.util.DEBUG_SESSION_PORT
import me.rafaelldi.aspire.util.DEBUG_SESSION_TOKEN
import me.rafaelldi.aspire.util.DOTNET_DASHBOARD_FRONTEND_BROWSERTOKEN
import me.rafaelldi.aspire.util.DOTNET_DASHBOARD_OTLP_ENDPOINT_URL
import me.rafaelldi.aspire.util.DOTNET_DASHBOARD_RESOURCESERVICE_APIKEY
import me.rafaelldi.aspire.util.DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS
import me.rafaelldi.aspire.util.DOTNET_RESOURCE_SERVICE_ENDPOINT_URL
import org.jetbrains.concurrency.await
import java.io.File
import java.net.URI
import java.util.*

class AspireHostExecutorFactory(
private val project: Project,
private val parameters: AspireHostConfigurationParameters
) : AsyncExecutorFactory {
companion object {
private const val ASPNETCORE_URLS = "ASPNETCORE_URLS"
private const val ASPIRE_ALLOW_UNSECURED_TRANSPORT = "ASPIRE_ALLOW_UNSECURED_TRANSPORT"
}

override suspend fun create(
executorId: String,
environment: ExecutionEnvironment,
@@ -65,32 +66,20 @@ class AspireHostExecutorFactory(
?: throw CantRunException("Unable to find project output")

val envs = parameters.envs.toMutableMap()

val debugSessionToken = UUID.randomUUID().toString()
val debugSessionPort = NetUtils.findFreePort(67800)
envs[DEBUG_SESSION_TOKEN] = debugSessionToken
envs[DEBUG_SESSION_PORT] = "localhost:$debugSessionPort"

val urls = envs[ASPNETCORE_URLS]
//see: https://learn.microsoft.com/en-us/dotnet/aspire/whats-new/preview-5?tabs=dotnet-cli#allow-unsecure-transport-for-http-endpoints
val useHttp = (urls != null && !urls.contains("https")) || envs.containsKey(ASPIRE_ALLOW_UNSECURED_TRANSPORT)

if (useHttp && !envs.containsKey(ASPIRE_ALLOW_UNSECURED_TRANSPORT)) {
envs[ASPIRE_ALLOW_UNSECURED_TRANSPORT] = "true"
}

if (!envs.containsKey(DOTNET_RESOURCE_SERVICE_ENDPOINT_URL)) {
val resourceEndpointPort = NetUtils.findFreePort(77800)
envs[DOTNET_RESOURCE_SERVICE_ENDPOINT_URL] =
if (useHttp) "http://localhost:$resourceEndpointPort"
else "https://localhost:$resourceEndpointPort"
}

if (!envs.containsKey(DOTNET_DASHBOARD_OTLP_ENDPOINT_URL)) {
val openTelemetryProtocolEndpointPort = NetUtils.findFreePort(87800)
envs[DOTNET_DASHBOARD_OTLP_ENDPOINT_URL] =
if (useHttp) "http://localhost:$openTelemetryProtocolEndpointPort"
else "https://localhost:$openTelemetryProtocolEndpointPort"
val environmentVariableValues = configureEnvironmentVariables(envs)

if (environmentVariableValues.browserToken != null) {
val url = URI(parameters.startBrowserParameters.url)
val updatedUrl = URI(
url.scheme,
null,
url.host,
url.port,
"/login",
"t=${environmentVariableValues.browserToken}",
null
)
parameters.startBrowserParameters.url = updatedUrl.toString()
}

val processOptions = ProjectProcessOptions(
@@ -126,4 +115,63 @@ class AspireHostExecutorFactory(
true
)
}

private fun configureEnvironmentVariables(envs: MutableMap<String, String>): EnvironmentVariableValues {
//Switch DCP to the IDE mode
//see: https://github.com/dotnet/aspire/blob/main/docs/specs/IDE-execution.md#enabling-ide-execution
val debugSessionToken = UUID.randomUUID().toString()
val debugSessionPort = NetUtils.findFreePort(47100)
envs[DEBUG_SESSION_TOKEN] = debugSessionToken
envs[DEBUG_SESSION_PORT] = "localhost:$debugSessionPort"

val urls = requireNotNull(envs[ASPNETCORE_URLS])
val isHttpUrl = !urls.contains("https")
val allowUnsecuredTransport = envs[ASPIRE_ALLOW_UNSECURED_TRANSPORT]?.equals("true", true) == true

//Automatically set the `ASPIRE_ALLOW_UNSECURED_TRANSPORT` environment variable if the `http` protocol is used
//see: https://learn.microsoft.com/en-us/dotnet/aspire/troubleshooting/allow-unsecure-transport
if (isHttpUrl && !allowUnsecuredTransport) {
envs[ASPIRE_ALLOW_UNSECURED_TRANSPORT] = "true"
}

val useHttp = isHttpUrl || allowUnsecuredTransport

//Set the DOTNET_RESOURCE_SERVICE_ENDPOINT_URL environment variable if not specified
if (!envs.containsKey(DOTNET_RESOURCE_SERVICE_ENDPOINT_URL)) {
val resourceEndpointPort = NetUtils.findFreePort(47200)
envs[DOTNET_RESOURCE_SERVICE_ENDPOINT_URL] =
if (useHttp) "http://localhost:$resourceEndpointPort"
else "https://localhost:$resourceEndpointPort"
}

//Set the DOTNET_DASHBOARD_OTLP_ENDPOINT_URL environment variable if not specified
if (!envs.containsKey(DOTNET_DASHBOARD_OTLP_ENDPOINT_URL)) {
val openTelemetryProtocolEndpointPort = NetUtils.findFreePort(47300)
envs[DOTNET_DASHBOARD_OTLP_ENDPOINT_URL] =
if (useHttp) "http://localhost:$openTelemetryProtocolEndpointPort"
else "https://localhost:$openTelemetryProtocolEndpointPort"
}

val allowAnonymousDashboard = envs[DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS]?.equals("true", true) == true

//Configure Dashboard frontend authentication
//see: https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/configuration#frontend-authentication
var browserToken: String? = null
if (!allowAnonymousDashboard) {
browserToken = UUID.randomUUID().toString()
envs[DOTNET_DASHBOARD_FRONTEND_BROWSERTOKEN] = browserToken
}

//Configure ApiKey for the Resource service
//see: https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/dashboard/configuration#resources
if (!allowAnonymousDashboard) {
envs[DOTNET_DASHBOARD_RESOURCESERVICE_APIKEY] = UUID.randomUUID().toString()
}

return EnvironmentVariableValues(browserToken)
}

private data class EnvironmentVariableValues(
val browserToken: String?
)
}
Loading

0 comments on commit 4073f83

Please sign in to comment.