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

Trying out gha to run tests #7073

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
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
83 changes: 83 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Run Integration Tests

on:
pull_request:
branches: [ main ]

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
davidfowl marked this conversation as resolved.
Show resolved Hide resolved
include:
davidfowl marked this conversation as resolved.
Show resolved Hide resolved
- project: tests/Aspire.Hosting.Elasticsearch.Tests/Aspire.Hosting.Elasticsearch.Tests.csproj
name: Elasticsearch
- project: tests/Aspire.Hosting.PostgreSQL.Tests/Aspire.Hosting.PostgreSQL.Tests.csproj
name: PostgreSQL
- project: tests/Aspire.Hosting.Oracle.Tests/Aspire.Hosting.Oracle.Tests.csproj
name: Oracle
- project: tests/Aspire.Hosting.Kafka.Tests/Aspire.Hosting.Kafka.Tests.csproj
name: Kafka
- project: tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj
name: Hosting
- project: tests/Aspire.Hosting.Redis.Tests/Aspire.Hosting.Redis.Tests.csproj
name: Redis
- project: tests/Aspire.Hosting.Azure.Tests/Aspire.Hosting.Azure.Tests.csproj
name: Azure
- project: tests/Aspire.Playground.Tests/Aspire.Playground.Tests.csproj
name: Playground
# Add more projects as needed
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up .NET Core
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.x
9.x

- name: Trust HTTPS development certificate
run: dotnet dev-certs https --trust

- name: Verify Docker is running
run: docker info

- name: Install Azure Functions Core Tools
run: |
sudo apt-get update
sudo apt-get install -y azure-functions-core-tools-4

- name: Restore dependencies
run: dotnet restore ${{ matrix.project }}

- name: Build projects
run: dotnet build ${{ matrix.project }} /p:CI=false --no-restore

- name: Run tests
id: run-tests
run: |
dotnet test ${{ matrix.project }} \
--logger "console;verbosity=normal" \
--logger "trx" \
--logger html \
--blame \
--blame-hang-timeout 7m \
--results-directory testresults \
--no-restore \
--no-build \
/p:CI=false

- name: Compress test results
if: always()
davidfowl marked this conversation as resolved.
Show resolved Hide resolved
run: zip -r testresults.zip testresults

- name: Upload test results artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: testresults-${{ matrix.name }}
path: testresults.zip
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ public static IResourceBuilder<ElasticsearchResource> WithDataBindMount(this IRe
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(source);

return builder.WithBindMount(source, "/usr/share/elasticsearch/data");
return builder.WithBindMount(source,
"/usr/share/elasticsearch/data",
isReadOnly: false,
fileMode: ElasticsearchResource.s_defaultUnixFileMode);
}
}
5 changes: 5 additions & 0 deletions src/Aspire.Hosting.Elasticsearch/ElasticsearchResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public class ElasticsearchResource : ContainerResource, IResourceWithConnectionS
//For things like cluster updates, master elections, nodes joining/leaving, shard allocation
internal const string InternalEndpointName = "internal";

internal static UnixFileMode s_defaultUnixFileMode =
UnixFileMode.GroupExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite |
UnixFileMode.OtherExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite |
UnixFileMode.UserExecute | UnixFileMode.UserRead | UnixFileMode.UserWrite;

/// <param name="name">The name of the resource.</param>
/// <param name="password">A parameter that contains the Elasticsearch superuser password.</param>
public ElasticsearchResource(string name, ParameterResource password) : base(name)
Expand Down
3 changes: 3 additions & 0 deletions src/Aspire.Hosting.PostgreSQL/PgAdminContainerResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ namespace Aspire.Hosting.Postgres;
/// <param name="name">The name of the container resource.</param>
public sealed class PgAdminContainerResource(string name) : ContainerResource(ThrowIfNull(name))
{
internal static UnixFileMode? s_defaultBindMountFileMode =
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead;

private static string ThrowIfNull([NotNull] string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null)
=> argument ?? throw new ArgumentNullException(paramName);
}
5 changes: 5 additions & 0 deletions src/Aspire.Hosting.PostgreSQL/PgWebContainerResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public sealed class PgWebContainerResource(string name) : ContainerResource(name
{
internal const string PrimaryEndpointName = "http";

internal static UnixFileMode s_defaultBindMountFileMode =
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;

private EndpointReference? _primaryEndpoint;

/// <summary>
Expand Down
16 changes: 4 additions & 12 deletions src/Aspire.Hosting.PostgreSQL/PostgresBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public static IResourceBuilder<T> WithPgAdmin<T>(this IResourceBuilder<T> builde
.WithImageRegistry(PostgresContainerImageTags.PgAdminRegistry)
.WithHttpEndpoint(targetPort: 80, name: "http")
.WithEnvironment(SetPgAdminEnvironmentVariables)
.WithBindMount(Path.GetTempFileName(), "/pgadmin4/servers.json")
.WithBindMount(Path.GetTempFileName(), "/pgadmin4/servers.json", isReadOnly: true, fileMode: PgAdminContainerResource.s_defaultBindMountFileMode)
.WithHttpHealthCheck("/browser")
.ExcludeFromManifest();

Expand All @@ -164,11 +164,6 @@ public static IResourceBuilder<T> WithPgAdmin<T>(this IResourceBuilder<T> builde

using var stream = new FileStream(serverFileMount.Source!, FileMode.Create);
using var writer = new Utf8JsonWriter(stream);
// Need to grant read access to the config file on unix like systems.
if (!OperatingSystem.IsWindows())
{
File.SetUnixFileMode(serverFileMount.Source!, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead);
}

var serverIndex = 1;

Expand Down Expand Up @@ -284,7 +279,7 @@ public static IResourceBuilder<PostgresServerResource> WithPgWeb(this IResourceB
.WithImage(PostgresContainerImageTags.PgWebImage, PostgresContainerImageTags.PgWebTag)
.WithImageRegistry(PostgresContainerImageTags.PgWebRegistry)
.WithHttpEndpoint(targetPort: 8081, name: "http")
.WithBindMount(dir, "/.pgweb/bookmarks")
.WithBindMount(dir, "/.pgweb/bookmarks", isReadOnly: false, fileMode: PgWebContainerResource.s_defaultBindMountFileMode)
.WithArgs("--bookmarks-dir=/.pgweb/bookmarks")
.WithArgs("--sessions")
.ExcludeFromManifest();
Expand All @@ -293,17 +288,14 @@ public static IResourceBuilder<PostgresServerResource> WithPgWeb(this IResourceB

pgwebContainerBuilder.WithRelationship(builder.Resource, "PgWeb");

pgwebContainerBuilder.WithHttpHealthCheck();

builder.ApplicationBuilder.Eventing.Subscribe<AfterEndpointsAllocatedEvent>(async (e, ct) =>
{
var adminResource = builder.ApplicationBuilder.Resources.OfType<PgWebContainerResource>().Single();
var serverFileMount = adminResource.Annotations.OfType<ContainerMountAnnotation>().Single(v => v.Target == "/.pgweb/bookmarks");
var postgresInstances = builder.ApplicationBuilder.Resources.OfType<PostgresDatabaseResource>();

if (!Directory.Exists(serverFileMount.Source!))
{
Directory.CreateDirectory(serverFileMount.Source!);
}

foreach (var postgresDatabase in postgresInstances)
{
var user = postgresDatabase.Parent.UserNameParameter?.Value ?? "postgres";
Expand Down
4 changes: 2 additions & 2 deletions src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ await pipeline.ExecuteAsync(async (ctx) =>
{
resourceLogger.LogError("Could not import Redis databases into RedisInsight. Reason: {reason}", ex.Message);
}
};
}
}
}

Expand Down Expand Up @@ -466,6 +466,6 @@ public static IResourceBuilder<RedisInsightResource> WithDataBindMount(this IRes
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(source);

return builder.WithBindMount(source, "/data");
return builder.WithBindMount(source, "/data", isReadOnly: false, fileMode: RedisInsightResource.s_defaultUnixFileMode);
}
}
5 changes: 5 additions & 0 deletions src/Aspire.Hosting.Redis/RedisInsightResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public class RedisInsightResource(string name) : ContainerResource(name)
{
internal const string PrimaryEndpointName = "http";

internal static UnixFileMode s_defaultUnixFileMode =
UnixFileMode.GroupExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite |
UnixFileMode.OtherExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite |
UnixFileMode.UserExecute | UnixFileMode.UserRead | UnixFileMode.UserWrite;

private EndpointReference? _primaryEndpoint;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public ContainerMountAnnotation(string? source, string target, ContainerMountTyp
/// </summary>
public string? Source { get; }

/// <summary>
/// Gets or sets the Unix file mode for the mount.
/// </summary>
public UnixFileMode? UnixFileMode { get; init; }

/// <summary>
/// Gets the target of the mount.
/// </summary>
Expand Down
23 changes: 21 additions & 2 deletions src/Aspire.Hosting/ContainerResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,32 @@ public static IResourceBuilder<T> WithVolume<T>(this IResourceBuilder<T> builder
/// <param name="target">The target path where the file or directory is mounted in the container.</param>
/// <param name="isReadOnly">A flag that indicates if this is a read-only mount.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithBindMount<T>(this IResourceBuilder<T> builder, string source, string target, bool isReadOnly = false) where T : ContainerResource
#pragma warning disable RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads
public static IResourceBuilder<T> WithBindMount<T>(this IResourceBuilder<T> builder, string source, string target, bool isReadOnly = false) where T : ContainerResource =>
builder.WithBindMount(source, target, isReadOnly, fileMode: null);
#pragma warning restore RS0027 // API with optional parameter(s) should have the most parameters amongst its public overloads

/// <summary>
/// Adds a bind mount to a container resource.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
/// <param name="source">The source path of the mount. This is the path to the file or directory on the host.</param>
/// <param name="target">The target path where the file or directory is mounted in the container.</param>
/// <param name="isReadOnly">A flag that indicates if this is a read-only mount.</param>
/// <param name="fileMode">The permissions to set on the file or directory on the host.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<T> WithBindMount<T>(this IResourceBuilder<T> builder, string source, string target, bool isReadOnly, UnixFileMode? fileMode) where T : ContainerResource
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(target);

var annotation = new ContainerMountAnnotation(Path.GetFullPath(source, builder.ApplicationBuilder.AppHostDirectory), target, ContainerMountType.BindMount, isReadOnly);
var annotation = new ContainerMountAnnotation(Path.GetFullPath(source, builder.ApplicationBuilder.AppHostDirectory), target, ContainerMountType.BindMount, isReadOnly)
{
UnixFileMode = fileMode
};

return builder.WithAnnotation(annotation);
}

Expand Down
19 changes: 18 additions & 1 deletion src/Aspire.Hosting/Dcp/ApplicationExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,23 @@ private void PrepareContainers()

foreach (var mount in containerMounts)
{
if (mount.Type == ContainerMountType.BindMount &&
mount.UnixFileMode is not null &&
Comment on lines +1500 to +1501
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evaluate the mount with pattern matching.

Suggested change
if (mount.Type == ContainerMountType.BindMount &&
mount.UnixFileMode is not null &&
if (mount is { Type: ContainerMountType.BindMount, UnixFileMode: not null } &&

!OperatingSystem.IsWindows())
{
// REVIEW: Should we know if it's a file or directory?
var source = mount.Source!;

if (File.Exists(source) || Directory.Exists(source))
{
File.SetUnixFileMode(source, mount.UnixFileMode.Value);
}
else if (!Directory.Exists(source))
{
Directory.CreateDirectory(source, mount.UnixFileMode.Value);
}
}

var volumeSpec = new VolumeMount
{
Source = mount.Source,
Expand Down Expand Up @@ -1728,7 +1745,7 @@ private async Task CreateContainerAsync(AppResource cr, ILogger resourceLogger,
resourceLogger.LogCritical(ex, "Failed to apply container runtime argument '{ConfigKey}'. A dependency may have failed to start.", arg);
_logger.LogDebug(ex, "Failed to apply container runtime argument '{ConfigKey}' to '{ResourceName}'. A dependency may have failed to start.", arg, modelContainerResource.Name);
failedToApplyConfiguration = true;
}
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/Aspire.Hosting/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#nullable enable
Aspire.Hosting.ApplicationModel.ContainerLifetime.Session = 0 -> Aspire.Hosting.ApplicationModel.ContainerLifetime
Aspire.Hosting.ApplicationModel.ContainerMountAnnotation.UnixFileMode.get -> System.IO.UnixFileMode?
Aspire.Hosting.ApplicationModel.ContainerMountAnnotation.UnixFileMode.init -> void
Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.HealthStatus.get -> Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus?
Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.Relationships.get -> System.Collections.Immutable.ImmutableArray<Aspire.Hosting.ApplicationModel.RelationshipSnapshot!>
Aspire.Hosting.ApplicationModel.CustomResourceSnapshot.Relationships.init -> void
Expand Down Expand Up @@ -232,6 +234,7 @@ Aspire.Hosting.ApplicationModel.ResourceNotificationService.WaitForResourceAsync
static Aspire.Hosting.ApplicationModel.ResourceExtensions.HasAnnotationIncludingAncestorsOfType<T>(this Aspire.Hosting.ApplicationModel.IResource! resource) -> bool
static Aspire.Hosting.ApplicationModel.ResourceExtensions.HasAnnotationOfType<T>(this Aspire.Hosting.ApplicationModel.IResource! resource) -> bool
static Aspire.Hosting.ApplicationModel.ResourceExtensions.TryGetAnnotationsIncludingAncestorsOfType<T>(this Aspire.Hosting.ApplicationModel.IResource! resource, out System.Collections.Generic.IEnumerable<T>? result) -> bool
static Aspire.Hosting.ContainerResourceBuilderExtensions.WithBindMount<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>! builder, string! source, string! target, bool isReadOnly, System.IO.UnixFileMode? fileMode) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>!
static Aspire.Hosting.ContainerResourceBuilderExtensions.WithBuildArg<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>! builder, string! name, object? value) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>!
static Aspire.Hosting.ContainerResourceBuilderExtensions.WithContainerName<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>! builder, string! name) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>!
static Aspire.Hosting.ContainerResourceBuilderExtensions.WithImageRegistry<T>(this Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>! builder, string? registry) -> Aspire.Hosting.ApplicationModel.IResourceBuilder<T!>!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,13 @@ public async Task VerifyWithPgWeb()

using var app = builder.Build();

var resourceNotificationService = app.Services.GetRequiredService<ResourceNotificationService>();

await app.StartAsync();

await app.WaitForTextAsync("Starting server...", resourceName: pgWebBuilder.Resource.Name);
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

var evt = await resourceNotificationService.WaitForResourceHealthyAsync(pgWebBuilder.Resource.Name, cts.Token);

var client = app.CreateHttpClient(pgWebBuilder.Resource.Name, "http");

Expand All @@ -177,6 +181,9 @@ public async Task VerifyWithPgWeb()

var response = await client.PostAsync("/api/connect", httpContent);
var d = await response.Content.ReadAsStringAsync();

testOutputHelper.WriteLine("RESPONSE: \r\n" + d);

response.EnsureSuccessStatusCode();
}

Expand Down
3 changes: 2 additions & 1 deletion tests/Aspire.Hosting.Redis.Tests/RedisFunctionalTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,8 @@ public async Task RedisInsightWithDataShouldPersistStateBetweenUsages(bool useVo
}
else
{
bindMountPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
bindMountPath = Directory.CreateTempSubdirectory().FullName;

redisInsightBuilder1.WithDataBindMount(bindMountPath);
}

Expand Down
Loading
Loading