Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to preview 6 #124

Merged
merged 9 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

## [Unreleased]

### Changed

- Support for .NET Aspire preview 6

## [0.5.0] - 2024-04-12

### Changed
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/dotnet/aspire-session-host/ParentProcessWatchdog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -19,6 +19,8 @@

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddEnvironmentVariables("Rider_");

builder.Services.AddGrpc();

var connection = new Connection(rdPort.Value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand All @@ -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)
Expand Down
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
Expand Up @@ -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 },
Expand All @@ -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
{
Expand All @@ -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
{
Expand All @@ -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
Expand Up @@ -19,23 +19,24 @@
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,
Expand Down Expand Up @@ -65,32 +66,20 @@
?: 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(
Expand All @@ -116,8 +105,8 @@
params.tfm ?: projectOutput.tfm,
params.workingDirectoryPath ?: projectOutput.workingDirectory,
params.commandLineArgumentString ?: ParametersListUtil.join(projectOutput.defaultArguments),
false,

Check notice on line 108 in src/main/kotlin/me/rafaelldi/aspire/run/AspireHostExecutorFactory.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Boolean literal argument without parameter name

Boolean literal argument without a parameter name
false,

Check notice on line 109 in src/main/kotlin/me/rafaelldi/aspire/run/AspireHostExecutorFactory.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Boolean literal argument without parameter name

Boolean literal argument without a parameter name
params.environmentVariables,
true,
parameters.startBrowserAction,
Expand All @@ -126,4 +115,63 @@
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
Loading