From beb0518c8b4fa96ca609aa5fac675ba4e9d75e1e Mon Sep 17 00:00:00 2001 From: Tim Heuer Date: Fri, 2 Feb 2024 15:12:21 -0800 Subject: [PATCH 1/3] Adds phpMyAdmin to MySql component This adds the phpMyAdmin container image when `WithPhpMyAdmin` is used on a MySql container. --- .../MySql/MySqlBuilderExtensions.cs | 30 +++++++ .../MySql/PhpMyAdminConfigWriterHook.cs | 85 +++++++++++++++++++ .../MySql/PhpMyAdminResource.cs | 12 +++ 3 files changed, 127 insertions(+) create mode 100644 src/Aspire.Hosting/MySql/PhpMyAdminConfigWriterHook.cs create mode 100644 src/Aspire.Hosting/MySql/PhpMyAdminResource.cs diff --git a/src/Aspire.Hosting/MySql/MySqlBuilderExtensions.cs b/src/Aspire.Hosting/MySql/MySqlBuilderExtensions.cs index 4dd9b0811ca..f99fa21c805 100644 --- a/src/Aspire.Hosting/MySql/MySqlBuilderExtensions.cs +++ b/src/Aspire.Hosting/MySql/MySqlBuilderExtensions.cs @@ -3,6 +3,8 @@ using System.Net.Sockets; using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Lifecycle; +using Aspire.Hosting.MySql; using Aspire.Hosting.Publishing; namespace Aspire.Hosting; @@ -73,6 +75,34 @@ public static IResourceBuilder AddDatabase(this IResource .WithManifestPublishingCallback(context => WriteMySqlDatabaseToManifest(context, mySqlDatabase)); } + /// + /// Adds a phpMyAdmin administration and development platform for MySql to the application model. + /// + /// The MySql server resource builder. + /// The host port for the application ui. + /// The name of the container (Optional). + /// A reference to the . + public static IResourceBuilder WithPhpMyAdmin(this IResourceBuilder builder, int? hostPort = null, string? containerName = null) where T : IMySqlParentResource + { + if (builder.ApplicationBuilder.Resources.OfType().Any()) + { + return builder; + } + + builder.ApplicationBuilder.Services.TryAddLifecycleHook(); + + containerName ??= $"{builder.Resource.Name}-phpmyadmin"; + + var phpMyAdminContainer = new PhpMyAdminContainerResource(containerName); + builder.ApplicationBuilder.AddResource(phpMyAdminContainer) + .WithAnnotation(new ContainerImageAnnotation { Image = "phpmyadmin", Tag = "latest" }) + .WithHttpEndpoint(containerPort: 80, hostPort: hostPort, name: containerName) + .WithVolumeMount(Path.GetTempFileName(), "/etc/phpmyadmin/config.user.inc.php") + .ExcludeFromManifest(); + + return builder; + } + private static void WriteMySqlContainerToManifest(ManifestPublishingContext context) { context.Writer.WriteString("type", "mysql.server.v0"); diff --git a/src/Aspire.Hosting/MySql/PhpMyAdminConfigWriterHook.cs b/src/Aspire.Hosting/MySql/PhpMyAdminConfigWriterHook.cs new file mode 100644 index 00000000000..57bf52b2064 --- /dev/null +++ b/src/Aspire.Hosting/MySql/PhpMyAdminConfigWriterHook.cs @@ -0,0 +1,85 @@ +// 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.ApplicationModel; +using Aspire.Hosting.Lifecycle; + +namespace Aspire.Hosting.MySql; +public class PhpMyAdminConfigWriterHook : IDistributedApplicationLifecycleHook +{ + public Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken) + { + var adminResource = appModel.Resources.OfType().Single(); + var serverFileMount = adminResource.Annotations.OfType().Single(v => v.Target == "/etc/phpmyadmin/config.user.inc.php"); + var mySqlInstances = appModel.Resources.OfType(); + + if (appModel.Resources.OfType().SingleOrDefault() is not { } myAdminResource) + { + // No-op if there is no myAdmin resource (removed after hook added). + return Task.CompletedTask; + } + + if (!mySqlInstances.Any()) + { + // No-op if there are no MySql resources present. + return Task.CompletedTask; + } + + if (mySqlInstances.Count() == 1) + { + var singleInstance = mySqlInstances.Single(); + if (singleInstance.TryGetAllocatedEndPoints(out var allocatedEndPoints)) + { + var endpoint = allocatedEndPoints.Where(ae => ae.Name == "tcp").Single(); + myAdminResource.Annotations.Add(new EnvironmentCallbackAnnotation((EnvironmentCallbackContext context) => + { + context.EnvironmentVariables.Add("PMA_HOST", $"host.docker.internal:{endpoint.Port}"); + context.EnvironmentVariables.Add("PMA_USER", "root"); + var password = singleInstance switch + { + MySqlServerResource psr => psr.Password, + MySqlContainerResource pcr => pcr.Password, + _ => throw new InvalidOperationException("MySql resource is neither MySqlServerResource or MySqlContainerResource.") + }; + context.EnvironmentVariables.Add("PMA_PASSWORD", password); + })); + } + } + else + { + using var stream = new FileStream(serverFileMount.Source, FileMode.Create); + using var writer = new StreamWriter(stream); + + writer.WriteLine(" psr.Password, + MySqlContainerResource pcr => pcr.Password, + _ => throw new InvalidOperationException("MySql resource is neither MySqlServerResource or MySqlContainerResource.") + }; + + var endpoint = allocatedEndpoints.Where(ae => ae.Name == "tcp").Single(); + writer.WriteLine("$i++;"); + writer.WriteLine($"$cfg['Servers'][$i]['host'] = 'host.docker.internal:{endpoint.Port}';"); + writer.WriteLine($"$cfg['Servers'][$i]['verbose'] = '{mySqlInstance.Name}';"); + writer.WriteLine($"$cfg['Servers'][$i]['auth_type'] = 'cookie';"); + writer.WriteLine($"$cfg['Servers'][$i]['user'] = 'root';"); + writer.WriteLine($"$cfg['Servers'][$i]['password'] = '{password}';"); + writer.WriteLine($"$cfg['Servers'][$i]['AllowNoPassword'] = true;"); + writer.WriteLine(); + } + } + writer.WriteLine("$cfg['DefaultServer'] = 1;"); + writer.WriteLine("?>"); + } + + return Task.CompletedTask; + } +} diff --git a/src/Aspire.Hosting/MySql/PhpMyAdminResource.cs b/src/Aspire.Hosting/MySql/PhpMyAdminResource.cs new file mode 100644 index 00000000000..9387ac08e2d --- /dev/null +++ b/src/Aspire.Hosting/MySql/PhpMyAdminResource.cs @@ -0,0 +1,12 @@ +// 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.ApplicationModel; + +namespace Aspire.Hosting.MySql; +internal sealed class PhpMyAdminContainerResource : ContainerResource +{ + public PhpMyAdminContainerResource(string name) : base(name) + { + } +} From 632c8bb5be6560eb009ba5d889ca9dcd1470bc26 Mon Sep 17 00:00:00 2001 From: Tim Heuer Date: Fri, 2 Feb 2024 16:41:05 -0800 Subject: [PATCH 2/3] Added tests --- .../MySql/MySqlFunctionalTests.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/Aspire.Hosting.Tests/MySql/MySqlFunctionalTests.cs b/tests/Aspire.Hosting.Tests/MySql/MySqlFunctionalTests.cs index a61d1777814..051fc47fe18 100644 --- a/tests/Aspire.Hosting.Tests/MySql/MySqlFunctionalTests.cs +++ b/tests/Aspire.Hosting.Tests/MySql/MySqlFunctionalTests.cs @@ -1,7 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Net.Sockets; +using System.Text.RegularExpressions; +using Aspire.Hosting.MySql; using Aspire.Hosting.Tests.Helpers; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Aspire.Hosting.Tests.MySql; @@ -32,4 +36,90 @@ public async Task VerifyMySqlWorks() Assert.True(response.IsSuccessStatusCode, responseContent); } + + [Fact] + public void WithMySqlTwiceEndsUpWithOneAdminContainer() + { + var builder = DistributedApplication.CreateBuilder(); + builder.AddMySql("mySql").WithPhpMyAdmin(); + builder.AddMySqlContainer("mySql2").WithPhpMyAdmin(); + + Assert.Single(builder.Resources.OfType()); + } + + [Fact] + public async Task SingleMySqlInstanceProducesCorrectMySqlHostsVariable() + { + var builder = DistributedApplication.CreateBuilder(); + var mysql = builder.AddMySql("mySql").WithPhpMyAdmin(); + var app = builder.Build(); + + // Add fake allocated endpoints. + mysql.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "host.docker.internal", 5001, "tcp")); + + var model = app.Services.GetRequiredService(); + var hook = new PhpMyAdminConfigWriterHook(); + await hook.AfterEndpointsAllocatedAsync(model, CancellationToken.None); + + var myAdmin = builder.Resources.Single(r => r.Name.EndsWith("-phpmyadmin")); + + var envAnnotations = myAdmin.Annotations.OfType(); + + var config = new Dictionary(); + var context = new EnvironmentCallbackContext("dcp", config); + + foreach (var annotation in envAnnotations) + { + annotation.Callback(context); + } + + Assert.Equal("host.docker.internal:5001", context.EnvironmentVariables["PMA_HOST"]); + Assert.NotNull(context.EnvironmentVariables["PMA_USER"]); + Assert.NotNull(context.EnvironmentVariables["PMA_PASSWORD"]); + } + + [Fact] + public void WithPhpMyAdminAddsContainer() + { + var builder = DistributedApplication.CreateBuilder(); + builder.AddMySql("mySql").WithPhpMyAdmin(); + + var container = builder.Resources.Single(r => r.Name == "mySql-phpmyadmin"); + var volume = container.Annotations.OfType().Single(); + + Assert.True(File.Exists(volume.Source)); // File should exist, but will be empty. + Assert.Equal("/etc/phpmyadmin/config.user.inc.php", volume.Target); + } + + [Fact] + public void WithPhpMyAdminProducesValidServerConfigFile() + { + var builder = DistributedApplication.CreateBuilder(); + var mysql1 = builder.AddMySql("mysql1").WithPhpMyAdmin(8081); + var mysql2 = builder.AddMySql("mysql2").WithPhpMyAdmin(8081); + + // Add fake allocated endpoints. + mysql1.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "host.docker.internal", 5001, "tcp")); + mysql2.WithAnnotation(new AllocatedEndpointAnnotation("tcp", ProtocolType.Tcp, "host.docker.internal", 5002, "tcp")); + + var myAdmin = builder.Resources.Single(r => r.Name.EndsWith("-phpmyadmin")); + var volume = myAdmin.Annotations.OfType().Single(); + + var app = builder.Build(); + var appModel = app.Services.GetRequiredService(); + + var hook = new PhpMyAdminConfigWriterHook(); + hook.AfterEndpointsAllocatedAsync(appModel, CancellationToken.None); + + using var stream = File.OpenRead(volume.Source); + var fileContents = new StreamReader(stream).ReadToEnd(); + + // check to see that the two hosts are in the file + string pattern1 = @"\$cfg\['Servers'\]\[\$i\]\['host'\] = 'host.docker.internal:5001';"; + string pattern2 = @"\$cfg\['Servers'\]\[\$i\]\['host'\] = 'host.docker.internal:5002';"; + Match match1 = Regex.Match(fileContents, pattern1); + Assert.True(match1.Success); + Match match2 = Regex.Match(fileContents, pattern2); + Assert.True(match2.Success); + } } From 6deffcc0ef4cb9866601b9b72a13563d71631ccf Mon Sep 17 00:00:00 2001 From: Tim Heuer Date: Fri, 2 Feb 2024 20:03:21 -0800 Subject: [PATCH 3/3] Added mysql playground --- Aspire.sln | 17 +++++ .../MySql.ApiService/MySql.ApiService.csproj | 22 ++++++ .../MySql.ApiService/MySql.ApiService.http | 26 +++++++ playground/mysql/MySql.ApiService/Program.cs | 76 +++++++++++++++++++ .../Properties/launchSettings.json | 41 ++++++++++ .../appsettings.Development.json | 8 ++ .../mysql/MySql.ApiService/appsettings.json | 9 +++ .../mysql/MySql.ApiService/data/init.sql | 25 ++++++ .../MySqlDb.AppHost/Directory.Build.props | 8 ++ .../MySqlDb.AppHost/Directory.Build.targets | 9 +++ .../MySqlDb.AppHost/MySqlDb.AppHost.csproj | 22 ++++++ playground/mysql/MySqlDb.AppHost/Program.cs | 16 ++++ .../Properties/launchSettings.json | 16 ++++ .../appsettings.Development.json | 8 ++ .../mysql/MySqlDb.AppHost/appsettings.json | 9 +++ 15 files changed, 312 insertions(+) create mode 100644 playground/mysql/MySql.ApiService/MySql.ApiService.csproj create mode 100644 playground/mysql/MySql.ApiService/MySql.ApiService.http create mode 100644 playground/mysql/MySql.ApiService/Program.cs create mode 100644 playground/mysql/MySql.ApiService/Properties/launchSettings.json create mode 100644 playground/mysql/MySql.ApiService/appsettings.Development.json create mode 100644 playground/mysql/MySql.ApiService/appsettings.json create mode 100644 playground/mysql/MySql.ApiService/data/init.sql create mode 100644 playground/mysql/MySqlDb.AppHost/Directory.Build.props create mode 100644 playground/mysql/MySqlDb.AppHost/Directory.Build.targets create mode 100644 playground/mysql/MySqlDb.AppHost/MySqlDb.AppHost.csproj create mode 100644 playground/mysql/MySqlDb.AppHost/Program.cs create mode 100644 playground/mysql/MySqlDb.AppHost/Properties/launchSettings.json create mode 100644 playground/mysql/MySqlDb.AppHost/appsettings.Development.json create mode 100644 playground/mysql/MySqlDb.AppHost/appsettings.json diff --git a/Aspire.sln b/Aspire.sln index e6b01fb2dad..50821834b47 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -225,6 +225,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParameterEndToEnd.AppHost", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParameterEndToEnd.ApiService", "playground\ParameterEndToEnd\ParameterEndToEnd.ApiService\ParameterEndToEnd.ApiService.csproj", "{FD63D574-8512-421D-B7FC-310AFA974361}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mysql", "mysql", "{621991F1-854A-4743-835B-10CAF11A0CFF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlDb.AppHost", "Playground\mysql\MySqlDb.AppHost\MySqlDb.AppHost.csproj", "{7E2AD00B-60E0-46C2-8640-7217D678F312}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySql.ApiService", "playground\mysql\MySql.ApiService\MySql.ApiService.csproj", "{F699F3AD-2AD9-454B-BA40-82AC3D6250FE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -599,6 +605,14 @@ Global {FD63D574-8512-421D-B7FC-310AFA974361}.Debug|Any CPU.Build.0 = Debug|Any CPU {FD63D574-8512-421D-B7FC-310AFA974361}.Release|Any CPU.ActiveCfg = Release|Any CPU {FD63D574-8512-421D-B7FC-310AFA974361}.Release|Any CPU.Build.0 = Release|Any CPU + {7E2AD00B-60E0-46C2-8640-7217D678F312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E2AD00B-60E0-46C2-8640-7217D678F312}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E2AD00B-60E0-46C2-8640-7217D678F312}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E2AD00B-60E0-46C2-8640-7217D678F312}.Release|Any CPU.Build.0 = Release|Any CPU + {F699F3AD-2AD9-454B-BA40-82AC3D6250FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F699F3AD-2AD9-454B-BA40-82AC3D6250FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F699F3AD-2AD9-454B-BA40-82AC3D6250FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F699F3AD-2AD9-454B-BA40-82AC3D6250FE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -705,6 +719,9 @@ Global {F1387494-34DE-4B31-B587-699B2E9A20CA} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} {54B66163-016D-4122-9364-409AB61BC36B} = {F1387494-34DE-4B31-B587-699B2E9A20CA} {FD63D574-8512-421D-B7FC-310AFA974361} = {F1387494-34DE-4B31-B587-699B2E9A20CA} + {621991F1-854A-4743-835B-10CAF11A0CFF} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} + {7E2AD00B-60E0-46C2-8640-7217D678F312} = {621991F1-854A-4743-835B-10CAF11A0CFF} + {F699F3AD-2AD9-454B-BA40-82AC3D6250FE} = {621991F1-854A-4743-835B-10CAF11A0CFF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C} diff --git a/playground/mysql/MySql.ApiService/MySql.ApiService.csproj b/playground/mysql/MySql.ApiService/MySql.ApiService.csproj new file mode 100644 index 00000000000..47cc5db1c56 --- /dev/null +++ b/playground/mysql/MySql.ApiService/MySql.ApiService.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + true + $(NoWarn);CS8002 + + + + + + + + + + + + + + diff --git a/playground/mysql/MySql.ApiService/MySql.ApiService.http b/playground/mysql/MySql.ApiService/MySql.ApiService.http new file mode 100644 index 00000000000..77a660c4a6c --- /dev/null +++ b/playground/mysql/MySql.ApiService/MySql.ApiService.http @@ -0,0 +1,26 @@ +@HostAddress = http://localhost:5168 + +GET {{HostAddress}}/catalog/ +Accept: application/json + +### + +GET {{HostAddress}}/catalog/1 +Accept: application/json + +### + +POST {{HostAddress}}/catalog/ +Content-Type: application/json + +{ + "name": "New Catalog Item", + "description": "New Catalog Description", + "price": 23.55 +} + +### +DELETE {{HostAddress}}/catalog/4 +Accept: application/json + +### diff --git a/playground/mysql/MySql.ApiService/Program.cs b/playground/mysql/MySql.ApiService/Program.cs new file mode 100644 index 00000000000..498f36a23a6 --- /dev/null +++ b/playground/mysql/MySql.ApiService/Program.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Dapper; +using MySqlConnector; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.AddServiceDefaults(); + +builder.Services.AddProblemDetails(); +builder.AddMySqlDataSource("Catalog"); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.MapGet("/catalog", async (MySqlConnection db) => +{ + const string sql = """ + SELECT Id, Name, Description, Price + FROM catalog + """; + + return await db.QueryAsync(sql); +}); + +app.MapGet("/catalog/{id}", async (int id, MySqlConnection db) => +{ + const string sql = """ + SELECT Id, Name, Description, Price + FROM catalog + WHERE Id = @id + """; + + return await db.QueryFirstOrDefaultAsync(sql, new { id }) is { } item + ? Results.Ok(item) + : Results.NotFound(); +}); + +app.MapPost("/catalog", async (CatalogItem item, MySqlConnection db) => +{ + const string sql = """ + INSERT INTO catalog (Name, Description, Price) + VALUES (@Name, @Description, @Price); + SELECT LAST_INSERT_ID(); + """; + + var id = await db.ExecuteScalarAsync(sql, item); + return Results.Created($"/catalog/{id}", id); +}); + +app.MapDelete("/catalog/{id}", async (int id, MySqlConnection db) => +{ + const string sql = """ + DELETE FROM catalog + WHERE Id = @id + """; + + var rows = await db.ExecuteAsync(sql, new { id }); + return rows > 0 ? Results.NoContent() : Results.NotFound(); +}); + +app.Run(); + +public record CatalogItem(int Id, string Name, string Description, decimal Price); diff --git a/playground/mysql/MySql.ApiService/Properties/launchSettings.json b/playground/mysql/MySql.ApiService/Properties/launchSettings.json new file mode 100644 index 00000000000..175501a6a2f --- /dev/null +++ b/playground/mysql/MySql.ApiService/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:49646", + "sslPort": 44383 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5168", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7203;http://localhost:5168", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/playground/mysql/MySql.ApiService/appsettings.Development.json b/playground/mysql/MySql.ApiService/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/playground/mysql/MySql.ApiService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/playground/mysql/MySql.ApiService/appsettings.json b/playground/mysql/MySql.ApiService/appsettings.json new file mode 100644 index 00000000000..10f68b8c8b4 --- /dev/null +++ b/playground/mysql/MySql.ApiService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/playground/mysql/MySql.ApiService/data/init.sql b/playground/mysql/MySql.ApiService/data/init.sql new file mode 100644 index 00000000000..0bfae9f08eb --- /dev/null +++ b/playground/mysql/MySql.ApiService/data/init.sql @@ -0,0 +1,25 @@ +-- MySql init script + +-- NOTE: MySql database and table names are case-sensitive on non-Windows platforms! +-- Column names are always case-insensitive. + +-- Create the Catalog table +CREATE TABLE IF NOT EXISTS `catalog` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `description` varchar(255) NOT NULL, + `price` DECIMAL(18,2) NOT NULL, + PRIMARY KEY (`id`) +); + +-- Insert some sample data into the Catalog table only if the table is empty +INSERT INTO catalog (name, description, price) +SELECT * +FROM ( + SELECT '.NET Bot Black Hoodie', 'This hoodie will keep you warm while looking cool and representing .NET!', 19.5 UNION ALL + SELECT '.NET Black & White Mug', 'The perfect place to keep your favorite beverage while you code.', 8.5 UNION ALL + SELECT 'Prism White T-Shirt', "It's a t-shirt, it's white, and it can be yours.", 12 + ) data +-- This clause ensures the rows are only inserted if the table is empty +WHERE NOT EXISTS (SELECT NULL FROM catalog) diff --git a/playground/mysql/MySqlDb.AppHost/Directory.Build.props b/playground/mysql/MySqlDb.AppHost/Directory.Build.props new file mode 100644 index 00000000000..b9b39c05e81 --- /dev/null +++ b/playground/mysql/MySqlDb.AppHost/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/playground/mysql/MySqlDb.AppHost/Directory.Build.targets b/playground/mysql/MySqlDb.AppHost/Directory.Build.targets new file mode 100644 index 00000000000..b7ba77268f8 --- /dev/null +++ b/playground/mysql/MySqlDb.AppHost/Directory.Build.targets @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/playground/mysql/MySqlDb.AppHost/MySqlDb.AppHost.csproj b/playground/mysql/MySqlDb.AppHost/MySqlDb.AppHost.csproj new file mode 100644 index 00000000000..a8f76b4e746 --- /dev/null +++ b/playground/mysql/MySqlDb.AppHost/MySqlDb.AppHost.csproj @@ -0,0 +1,22 @@ + + + + Exe + net8.0 + enable + enable + true + + + + + + + + + + + + + + diff --git a/playground/mysql/MySqlDb.AppHost/Program.cs b/playground/mysql/MySqlDb.AppHost/Program.cs new file mode 100644 index 00000000000..73cb11abfe2 --- /dev/null +++ b/playground/mysql/MySqlDb.AppHost/Program.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var builder = DistributedApplication.CreateBuilder(args); + +var catalogDbName = "catalog"; // MySql database & table names are case-sensitive on non-Windows. +var catalogDb = builder.AddMySqlContainer("mysql") + .WithEnvironment("MYSQL_DATABASE", catalogDbName) + .WithVolumeMount("../MySql.ApiService/data", "/docker-entrypoint-initdb.d", VolumeMountType.Bind) + .WithPhpMyAdmin() + .AddDatabase(catalogDbName); + +builder.AddProject("apiservice") + .WithReference(catalogDb); + +builder.Build().Run(); diff --git a/playground/mysql/MySqlDb.AppHost/Properties/launchSettings.json b/playground/mysql/MySqlDb.AppHost/Properties/launchSettings.json new file mode 100644 index 00000000000..4397aca491e --- /dev/null +++ b/playground/mysql/MySqlDb.AppHost/Properties/launchSettings.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15224", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16160" + } + } + } +} diff --git a/playground/mysql/MySqlDb.AppHost/appsettings.Development.json b/playground/mysql/MySqlDb.AppHost/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/playground/mysql/MySqlDb.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/playground/mysql/MySqlDb.AppHost/appsettings.json b/playground/mysql/MySqlDb.AppHost/appsettings.json new file mode 100644 index 00000000000..31c092aa450 --- /dev/null +++ b/playground/mysql/MySqlDb.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +}