|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Aspire.Hosting.Ats;
namespace Aspire.Hosting.CodeGeneration.Go;
/// <summary>
/// Provides language support for Go AppHosts.
/// Implements scaffolding, detection, and runtime configuration.
/// </summary>
public sealed class GoLanguageSupport : ILanguageSupport
{
/// <summary>
/// The language/runtime identifier for Go.
/// </summary>
private const string LanguageId = "go";
/// <summary>
/// The code generation target language. This maps to the ICodeGenerator.Language property.
/// </summary>
private const string CodeGenTarget = "Go";
private const string LanguageDisplayName = "Go";
private static readonly string[] s_detectionPatterns = ["apphost.go"];
/// <inheritdoc />
public string Language => LanguageId;
/// <inheritdoc />
public Dictionary<string, string> Scaffold(ScaffoldRequest request)
{
var files = new Dictionary<string, string>();
// Create apphost.go
files["apphost.go"] = """
// Aspire Go AppHost
// For more information, see: https://aspire.dev
package main
import (
"log"
"apphost/modules/aspire"
)
func main() {
builder, err := aspire.CreateBuilder(nil)
if err != nil {
log.Fatalf("Failed to create builder: %v", err)
}
// Add your resources here, for example:
// redis, _ := builder.AddRedis("cache")
// postgres, _ := builder.AddPostgres("db")
app, err := builder.Build()
if err != nil {
log.Fatalf("Failed to build: %v", err)
}
if err := app.Run(nil); err != nil {
log.Fatalf("Failed to run: %v", err)
}
}
""";
// Create go.mod with require and replace directives for local modules
// Go requires both a `require` directive and a `replace` directive for local modules
files["go.mod"] = """
module apphost
go 1.23
require apphost/modules/aspire v0.0.0
replace apphost/modules/aspire => ./.modules
""";
// Create apphost.run.json with random ports
var random = request.PortSeed.HasValue
? new Random(request.PortSeed.Value)
: Random.Shared;
var httpsPort = random.Next(10000, 65000);
var httpPort = random.Next(10000, 65000);
var otlpPort = random.Next(10000, 65000);
var resourceServicePort = random.Next(10000, 65000);
files["apphost.run.json"] = $$"""
{
"profiles": {
"https": {
"applicationUrl": "https://localhost:{{httpsPort}};http://localhost:{{httpPort}}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:{{otlpPort}}",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:{{resourceServicePort}}"
}
}
}
}
""";
return files;
}
/// <inheritdoc />
public DetectionResult Detect(string directoryPath)
{
var appHostPath = Path.Combine(directoryPath, "apphost.go");
if (!File.Exists(appHostPath))
{
return DetectionResult.NotFound;
}
var goModPath = Path.Combine(directoryPath, "go.mod");
if (!File.Exists(goModPath))
{
return DetectionResult.NotFound;
}
return DetectionResult.Found(LanguageId, "apphost.go");
}
/// <inheritdoc />
public RuntimeSpec GetRuntimeSpec()
{
// Note: InstallDependencies is null because "go run ." handles module
// resolution automatically, and InstallDependencies runs BEFORE code
// generation which means the .modules directory doesn't exist yet.
return new RuntimeSpec
{
Language = LanguageId,
DisplayName = LanguageDisplayName,
CodeGenLanguage = CodeGenTarget,
DetectionPatterns = s_detectionPatterns,
InstallDependencies = null,
Execute = new CommandSpec
{
Command = "go",
Args = ["run", "."]
}
};
}
}
|