Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// A resource that represents a Go module installer that runs go mod tidy or go mod download.
/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="workingDirectory">The working directory to use for the command.</param>
public class GoModInstallerResource(string name, string workingDirectory)
: ExecutableResource(name, "go", workingDirectory);
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,68 @@ private static string GetDefaultGoBaseImage(string workingDirectory, IServicePro
logger.LogDebug("No Go version detected, will use default version");
return null;
}

/// <summary>
/// Ensures Go module dependencies are tidied before the application starts using <c>go mod tidy</c>.
/// </summary>
/// <param name="builder">The Golang app resource builder.</param>
/// <param name="configureInstaller">Optional action to configure the installer resource.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<GolangAppExecutableResource> WithGoModTidy(
this IResourceBuilder<GolangAppExecutableResource> builder,
Action<IResourceBuilder<GoModInstallerResource>>? configureInstaller = null)
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));

// Only install packages during development, not in publish mode
if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
var installerName = $"{builder.Resource.Name}-go-mod-tidy";
var installer = new GoModInstallerResource(installerName, builder.Resource.WorkingDirectory);

var installerBuilder = builder.ApplicationBuilder.AddResource(installer)
.WithArgs("mod", "tidy")
.WithParentRelationship(builder.Resource)
.ExcludeFromManifest();

// Make the parent resource wait for the installer to complete
builder.WaitForCompletion(installerBuilder);

configureInstaller?.Invoke(installerBuilder);
}

return builder;
}

/// <summary>
/// Ensures Go module dependencies are downloaded before the application starts using <c>go mod download</c>.
/// </summary>
/// <param name="builder">The Golang app resource builder.</param>
/// <param name="configureInstaller">Optional action to configure the installer resource.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<GolangAppExecutableResource> WithGoModDownload(
this IResourceBuilder<GolangAppExecutableResource> builder,
Action<IResourceBuilder<GoModInstallerResource>>? configureInstaller = null)
{
ArgumentNullException.ThrowIfNull(builder, nameof(builder));

// Only install packages during development, not in publish mode
if (!builder.ApplicationBuilder.ExecutionContext.IsPublishMode)
{
var installerName = $"{builder.Resource.Name}-go-mod-download";
var installer = new GoModInstallerResource(installerName, builder.Resource.WorkingDirectory);

var installerBuilder = builder.ApplicationBuilder.AddResource(installer)
.WithArgs("mod", "download")
.WithParentRelationship(builder.Resource)
.ExcludeFromManifest();

// Make the parent resource wait for the installer to complete
builder.WaitForCompletion(installerBuilder);

configureInstaller?.Invoke(installerBuilder);
}

return builder;
}
}
39 changes: 39 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.Golang/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,45 @@ To have the Golang application listen on the correct port, you can use the follo
r.Run(":"+os.Getenv("PORT"))
```

## Dependency Management

The integration provides support for Go module dependency management using `go mod tidy` or `go mod download`.

### Using `go mod tidy`

To run `go mod tidy` before your application starts (to clean up and verify dependencies):

```csharp
var golang = builder.AddGolangApp("golang", "../gin-api")
.WithGoModTidy()
.WithHttpEndpoint(env: "PORT");
```

### Using `go mod download`

To run `go mod download` before your application starts (to download dependencies without verification):

```csharp
var golang = builder.AddGolangApp("golang", "../gin-api")
.WithGoModDownload()
.WithHttpEndpoint(env: "PORT");
```

Both methods create an installer resource that runs before your application starts, ensuring dependencies are available. The installer resource appears as a child resource in the Aspire dashboard.

You can also customize the installer resource using the optional `configureInstaller` parameter:

```csharp
var golang = builder.AddGolangApp("golang", "../gin-api")
.WithGoModTidy(configureInstaller: installer =>
{
installer.WithEnvironment("GOPROXY", "https://proxy.golang.org,direct");
})
.WithHttpEndpoint(env: "PORT");
```

> **Note:** The `WithGoModTidy` and `WithGoModDownload` methods only run during development. When publishing, the generated Dockerfile handles dependency management automatically.

## Publishing

When publishing your Aspire application, the Golang resource automatically generates a multi-stage Dockerfile for containerization. This means you don't need to manually create a Dockerfile for your Golang application.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,98 @@ public async Task GolangAppWithExecutableAsync()
}
);
}

[Fact]
public void GolangAppWithGoModTidyCreatesInstallerResource()
{
var builder = DistributedApplication.CreateBuilder();

builder.AddGolangApp("golang", "../../examples/golang/gin-api").WithGoModTidy();

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var golangResource = Assert.Single(appModel.Resources.OfType<GolangAppExecutableResource>());
var installerResource = Assert.Single(appModel.Resources.OfType<GoModInstallerResource>());

Assert.Equal("golang-go-mod-tidy", installerResource.Name);
Assert.Equal("go", installerResource.Command);
}

[Fact]
public async Task GolangAppWithGoModTidyHasCorrectArgsAsync()
{
var builder = DistributedApplication.CreateBuilder();

builder.AddGolangApp("golang", "../../examples/golang/gin-api").WithGoModTidy();

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var installerResource = Assert.Single(appModel.Resources.OfType<GoModInstallerResource>());

var args = await installerResource.GetArgumentValuesAsync();
Assert.Collection(
args,
arg => Assert.Equal("mod", arg),
arg => Assert.Equal("tidy", arg)
);
}

[Fact]
public void GolangAppWithGoModDownloadCreatesInstallerResource()
{
var builder = DistributedApplication.CreateBuilder();

builder.AddGolangApp("golang", "../../examples/golang/gin-api").WithGoModDownload();

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var golangResource = Assert.Single(appModel.Resources.OfType<GolangAppExecutableResource>());
var installerResource = Assert.Single(appModel.Resources.OfType<GoModInstallerResource>());

Assert.Equal("golang-go-mod-download", installerResource.Name);
Assert.Equal("go", installerResource.Command);
}

[Fact]
public async Task GolangAppWithGoModDownloadHasCorrectArgsAsync()
{
var builder = DistributedApplication.CreateBuilder();

builder.AddGolangApp("golang", "../../examples/golang/gin-api").WithGoModDownload();

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var installerResource = Assert.Single(appModel.Resources.OfType<GoModInstallerResource>());

var args = await installerResource.GetArgumentValuesAsync();
Assert.Collection(
args,
arg => Assert.Equal("mod", arg),
arg => Assert.Equal("download", arg)
);
}

[Fact]
public void WithGoModTidyNullBuilderThrows()
{
IResourceBuilder<GolangAppExecutableResource> builder = null!;

Assert.Throws<ArgumentNullException>(() => builder.WithGoModTidy());
}

[Fact]
public void WithGoModDownloadNullBuilderThrows()
{
IResourceBuilder<GolangAppExecutableResource> builder = null!;

Assert.Throws<ArgumentNullException>(() => builder.WithGoModDownload());
}
}
Loading