From fc8f4fee12f19bf1f909593e1d9060e8d0a38aa8 Mon Sep 17 00:00:00 2001 From: Foxtrek_64 Date: Sat, 8 Mar 2025 14:18:36 -0600 Subject: [PATCH 1/3] Initial implementation --- CommunityToolkit.Aspire.sln | 7 ++ ...unityToolkit.Aspire.Hosting.Zitadel.csproj | 13 ++++ .../ZitadelContainerImageTags.cs | 19 ++++++ .../ZitadelResource.cs | 35 ++++++++++ .../ZitadelResourceBuilderExtensions.cs | 67 +++++++++++++++++++ 5 files changed, 141 insertions(+) create mode 100644 src/CommunityToolkit.Aspire.Hosting.Zitadel/CommunityToolkit.Aspire.Hosting.Zitadel.csproj create mode 100644 src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelContainerImageTags.cs create mode 100644 src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs create mode 100644 src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResourceBuilderExtensions.cs diff --git a/CommunityToolkit.Aspire.sln b/CommunityToolkit.Aspire.sln index 115a70b66..d790200e5 100644 --- a/CommunityToolkit.Aspire.sln +++ b/CommunityToolkit.Aspire.sln @@ -365,6 +365,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hos EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.Dapr", "src\CommunityToolkit.Aspire.Hosting.Dapr\CommunityToolkit.Aspire.Hosting.Dapr.csproj", "{27144ED2-9F74-4A86-AABA-38CD061D8984}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.Zitadel", "src\CommunityToolkit.Aspire.Hosting.Zitadel\CommunityToolkit.Aspire.Hosting.Zitadel.csproj", "{35C570A9-8109-4DE0-8F67-B665DF56BE2B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -955,6 +957,10 @@ Global {27144ED2-9F74-4A86-AABA-38CD061D8984}.Debug|Any CPU.Build.0 = Debug|Any CPU {27144ED2-9F74-4A86-AABA-38CD061D8984}.Release|Any CPU.ActiveCfg = Release|Any CPU {27144ED2-9F74-4A86-AABA-38CD061D8984}.Release|Any CPU.Build.0 = Release|Any CPU + {35C570A9-8109-4DE0-8F67-B665DF56BE2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35C570A9-8109-4DE0-8F67-B665DF56BE2B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35C570A9-8109-4DE0-8F67-B665DF56BE2B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35C570A9-8109-4DE0-8F67-B665DF56BE2B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1138,6 +1144,7 @@ Global {72652F7E-CFC0-4E3D-AD96-74C206A110BA} = {41ACF613-EE5A-5900-F4D1-9FB713A32BE8} {92D490BC-B953-45DC-8F9D-A992B2AEF96A} = {41ACF613-EE5A-5900-F4D1-9FB713A32BE8} {27144ED2-9F74-4A86-AABA-38CD061D8984} = {41ACF613-EE5A-5900-F4D1-9FB713A32BE8} + {35C570A9-8109-4DE0-8F67-B665DF56BE2B} = {414151D4-7009-4E78-A5C6-D99EBD1E67D1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {08B1D4B8-D2C5-4A64-BB8B-E1A2B29525F0} diff --git a/src/CommunityToolkit.Aspire.Hosting.Zitadel/CommunityToolkit.Aspire.Hosting.Zitadel.csproj b/src/CommunityToolkit.Aspire.Hosting.Zitadel/CommunityToolkit.Aspire.Hosting.Zitadel.csproj new file mode 100644 index 000000000..2d9708ec2 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.Zitadel/CommunityToolkit.Aspire.Hosting.Zitadel.csproj @@ -0,0 +1,13 @@ + + + + Zitadel + Provides Zitadel IDP integration for Aspire. + + + + + + + + diff --git a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelContainerImageTags.cs new file mode 100644 index 000000000..442d4b49a --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelContainerImageTags.cs @@ -0,0 +1,19 @@ +namespace CommunityToolkit.Aspire.Hosting.Zitadel; + +internal static class ZitadelContainerImageTags +{ + /// + /// ghcr.io + /// + public const string Registry = "ghcr.io"; + + /// + /// zitadel/zitadel + /// + public const string Image = "zitadel/zitadel"; + + /// + /// latest + /// + public const string Tag = "latest"; +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs new file mode 100644 index 000000000..0ae6bf965 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs @@ -0,0 +1,35 @@ +// 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; +using Aspire.Hosting.ApplicationModel; +using System.Security.Cryptography.Xml; + +namespace CommunityToolkit.Aspire.Hosting.Zitadel; + +/// +/// A resource that represents a Zitadel resource. +/// The name of the resource. +/// A parameter that contains the Zitadel admin, or to use a default value. +/// A parameter that contains the Zitadel admin password. +/// +public sealed class ZitadelResource(string name, ParameterResource? admin, ParameterResource adminPassword) + : ContainerResource(name), IResourceWithServiceDiscovery +{ + private const string DefaultAdmin = "zitadel-admin"; + + /// + /// Gets the parameter that contains the Zitadel admin username. + /// + public ParameterResource? AdminUserNameParameter { get; } = admin; + + internal ReferenceExpression AdminReference + => AdminUserNameParameter is not null + ? ReferenceExpression.Create($"{AdminUserNameParameter}") + : ReferenceExpression.Create($"{DefaultAdmin}"); + + /// + /// Gets the parameter that contains the Zitadel admin password. + /// + public ParameterResource AdminPasswordParameter { get; } = adminPassword ?? throw new ArgumentNullException(nameof(adminPassword)); +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResourceBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResourceBuilderExtensions.cs new file mode 100644 index 000000000..9066b3ebe --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResourceBuilderExtensions.cs @@ -0,0 +1,67 @@ +// 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; +using Aspire.Hosting.ApplicationModel; + +namespace CommunityToolkit.Aspire.Hosting.Zitadel; + +/// +/// Provides extension methods for adding Zitadel resources to an . +/// +public static class ZitadelResourceBuilderExtensions +{ + private const string AdminEnvVarName = "ZITADEL_DEFAULTINSTANCE_ORG_HUMAN_USERNAME"; + private const string AdminEnvVarPassword = "ZITADEL_DEFAULTINSTANCE_ORG_HUMAN_PASSWORD"; + + private const int DefaultcontainerPort = 8080; // ZITADEL_PORT + private const string ManagementEndpointName = "management"; + + /// + /// Adds a Zitadel container to the application model. + /// + /// The . + /// The name of the resource. + /// The host port that the underlying container is bound to when running locally. + /// The parameter used as the admin for the Zitadel resource. If a default value will be used. + /// The parameter used as the admin password for the Zitadel resource. If a default password will be used. + /// A reference to the . + public static IResourceBuilder AddZitadel + ( + this IDistributedApplicationBuilder builder, + string name, + int? port = null, + IResourceBuilder? adminUsername = null, + IResourceBuilder? adminPassword = null + ) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrEmpty(name); + + var passwordParameter = adminPassword?.Resource ?? + builder.AddParameter + ( + name: $"{name}-password", + valueGetter: () => "Password1!", + secret: true + ).Resource; + + var resource = new ZitadelResource(name, adminUsername?.Resource, passwordParameter); + + // TODO: Add health check + var zitadel = builder + .AddResource(resource) + .WithImage(ZitadelContainerImageTags.Image) + .WithImageRegistry(ZitadelContainerImageTags.Registry) + .WithImageTag(ZitadelContainerImageTags.Tag) + .WithHttpEndpoint(port: port, targetPort: DefaultcontainerPort) + .WithEnvironment(context => + { + context.EnvironmentVariables[AdminEnvVarName] = resource.AdminReference; + context.EnvironmentVariables[AdminEnvVarPassword] = resource.AdminPasswordParameter; + // TODO: Implement Postgres/Cockroach DB integration. See https://zitadel.com/docs/self-hosting/deploy/compose + }); + + return zitadel; + } +} \ No newline at end of file From c75e4a7ee4acf6695d021f3055a35956ce71ecb6 Mon Sep 17 00:00:00 2001 From: Foxtrek_64 Date: Sat, 8 Mar 2025 14:26:03 -0600 Subject: [PATCH 2/3] Add missing XML comments --- .../ZitadelContainerImageTags.cs | 3 +++ src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelContainerImageTags.cs index 442d4b49a..249c2891f 100644 --- a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelContainerImageTags.cs +++ b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelContainerImageTags.cs @@ -1,5 +1,8 @@ namespace CommunityToolkit.Aspire.Hosting.Zitadel; +/// +/// Contains strings which describes the Zitadel container registry. +/// internal static class ZitadelContainerImageTags { /// diff --git a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs index 0ae6bf965..44c20d8bc 100644 --- a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResource.cs @@ -23,6 +23,9 @@ public sealed class ZitadelResource(string name, ParameterResource? admin, Param /// public ParameterResource? AdminUserNameParameter { get; } = admin; + /// + /// Gets the parameter that contains the Zitadel admin username. + /// internal ReferenceExpression AdminReference => AdminUserNameParameter is not null ? ReferenceExpression.Create($"{AdminUserNameParameter}") From d12d2b40dce46d2d16e3e84544fa9038c1f99350 Mon Sep 17 00:00:00 2001 From: Foxtrek_64 Date: Mon, 24 Mar 2025 11:29:26 -0500 Subject: [PATCH 3/3] Update password to be generated rather than having a default value --- .../CommunityToolkit.Aspire.Hosting.Zitadel.csproj | 1 - .../ZitadelResourceBuilderExtensions.cs | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/CommunityToolkit.Aspire.Hosting.Zitadel/CommunityToolkit.Aspire.Hosting.Zitadel.csproj b/src/CommunityToolkit.Aspire.Hosting.Zitadel/CommunityToolkit.Aspire.Hosting.Zitadel.csproj index 2d9708ec2..0975034df 100644 --- a/src/CommunityToolkit.Aspire.Hosting.Zitadel/CommunityToolkit.Aspire.Hosting.Zitadel.csproj +++ b/src/CommunityToolkit.Aspire.Hosting.Zitadel/CommunityToolkit.Aspire.Hosting.Zitadel.csproj @@ -7,7 +7,6 @@ - diff --git a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResourceBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResourceBuilderExtensions.cs index 9066b3ebe..ada9f48c0 100644 --- a/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResourceBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.Zitadel/ZitadelResourceBuilderExtensions.cs @@ -39,12 +39,7 @@ public static IResourceBuilder AddZitadel ArgumentException.ThrowIfNullOrEmpty(name); var passwordParameter = adminPassword?.Resource ?? - builder.AddParameter - ( - name: $"{name}-password", - valueGetter: () => "Password1!", - secret: true - ).Resource; + ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, $"{name}-password"); var resource = new ZitadelResource(name, adminUsername?.Resource, passwordParameter);