diff --git a/src/Aspire.Hosting.Kubernetes/Extensions/HelmExtensions.cs b/src/Aspire.Hosting.Kubernetes/Extensions/HelmExtensions.cs index 421cfd1d4f8..53a510e5a39 100644 --- a/src/Aspire.Hosting.Kubernetes/Extensions/HelmExtensions.cs +++ b/src/Aspire.Hosting.Kubernetes/Extensions/HelmExtensions.cs @@ -74,4 +74,14 @@ public static bool ContainsHelmExpression(this string value) public static bool ContainsHelmSecretExpression(this string value) => value.Contains($"{{{{ {ValuesSegment}.{SecretsKey}.", StringComparison.Ordinal); + + /// + /// Converts the specified environment variable key into a valid Helm key. + /// + /// + /// The environment variable names in Helm values files can not contain hyphens ('-'). + /// https://helm.sh/docs/chart_best_practices/values/ + /// + public static string ToHelmEnvironmentVariable(this string key) + => $"{key.Replace("-", "_")}"; } diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesResource.cs b/src/Aspire.Hosting.Kubernetes/KubernetesResource.cs index 45f5e57ccc4..b097a74e943 100644 --- a/src/Aspire.Hosting.Kubernetes/KubernetesResource.cs +++ b/src/Aspire.Hosting.Kubernetes/KubernetesResource.cs @@ -260,7 +260,7 @@ private async Task ProcessEnvironmentAsync(KubernetesEnvironmentContext environm foreach (var environmentVariable in context.EnvironmentVariables) { - var key = environmentVariable.Key; + var key = environmentVariable.Key.ToHelmEnvironmentVariable(); var value = await this.ProcessValueAsync(environmentContext, executionContext, environmentVariable.Value).ConfigureAwait(false); switch (value) diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs b/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs index f522d302337..0378611c172 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs +++ b/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs @@ -184,9 +184,23 @@ public async Task PublishAsync_HandlesSpecialResourceName() var cs = builder.AddConnectionString("api-cs", ReferenceExpression.Create($"Url={param0}, Secret={param1}")); var param3 = builder.AddResource(ParameterResourceBuilderExtensions.CreateDefaultPasswordParameter(builder, "param3")); - builder.AddProject("SpeciaL-ApP", launchProfileName: null) + + var containerResource = builder.AddContainer("Special-resource-with-hyphens", "my-image") + .AsHttp2Service() + .WithEnvironment("ORIGINAL_ENV", "value") + .WithHttpEndpoint(port: 80, targetPort: 80) + .WithHttpsEndpoint(port: 443, targetPort: 443) + .PublishAsKubernetesService(serviceResource => + { + serviceResource.Workload!.PodTemplate.Spec.Containers[0].ImagePullPolicy = "Always"; + (serviceResource.Workload as Deployment)!.Spec.RevisionHistoryLimit = 5; + }); + + var projectResource = builder.AddProject("SpeciaL-ApP", launchProfileName: null) .WithEnvironment("param3", param3) - .WithReference(cs); + .WithReference(cs) + .WithReference(containerResource.GetEndpoint("http")) + .WithReference(containerResource.GetEndpoint("https")); var app = builder.Build(); @@ -195,12 +209,15 @@ public async Task PublishAsync_HandlesSpecialResourceName() // Assert var expectedFiles = new[] { - "Chart.yaml", - "values.yaml", - "templates/SpeciaL-ApP/deployment.yaml", - "templates/SpeciaL-ApP/config.yaml", - "templates/SpeciaL-ApP/secrets.yaml" - }; + "Chart.yaml", + "values.yaml", + "templates/SpeciaL-ApP/deployment.yaml", + "templates/SpeciaL-ApP/config.yaml", + "templates/SpeciaL-ApP/secrets.yaml", + "templates/Special-resource-with-hyphens/deployment.yaml", + "templates/Special-resource-with-hyphens/service.yaml", + "templates/Special-resource-with-hyphens/config.yaml" + }; SettingsTask settingsTask = default!; diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml index d85636467fd..4f84aba059e 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml @@ -1,11 +1,15 @@ -parameters: +parameters: SpeciaL_ApP: SpeciaL_ApP_image: "SpeciaL-ApP:latest" secrets: SpeciaL_ApP: param3: "" config: + Special_resource_with_hyphens: + ORIGINAL_ENV: "value" SpeciaL_ApP: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + services__Special_resource_with_hyphens__http__0: "http://special-resource-with-hyphens-service:80" + services__Special_resource_with_hyphens__https__0: "https://special-resource-with-hyphens-service:443" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#03.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#03.verified.yaml index 4d50167c5b9..827b9086912 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#03.verified.yaml +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#03.verified.yaml @@ -1,4 +1,4 @@ ---- +--- apiVersion: "v1" kind: "ConfigMap" metadata: @@ -11,3 +11,5 @@ data: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "{{ .Values.config.SpeciaL_ApP.OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES }}" OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "{{ .Values.config.SpeciaL_ApP.OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES }}" OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "{{ .Values.config.SpeciaL_ApP.OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY }}" + services__Special_resource_with_hyphens__http__0: "{{ .Values.config.SpeciaL_ApP.services__Special_resource_with_hyphens__http__0 }}" + services__Special_resource_with_hyphens__https__0: "{{ .Values.config.SpeciaL_ApP.services__Special_resource_with_hyphens__https__0 }}" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml index b959b404a96..3574b4bab9b 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml @@ -1,4 +1,4 @@ ---- +--- apiVersion: "v1" kind: "Secret" metadata: @@ -9,5 +9,5 @@ metadata: app.kubernetes.io/instance: "{{.Release.Name}}" stringData: param3: "{{ .Values.secrets.SpeciaL_ApP.param3 }}" - ConnectionStrings__api-cs: "Url={{ .Values.config.SpeciaL_ApP.param0 }}, Secret={{ .Values.secrets.SpeciaL_ApP.param1 }}" + ConnectionStrings__api_cs: "Url={{ .Values.config.SpeciaL_ApP.param0 }}, Secret={{ .Values.secrets.SpeciaL_ApP.param1 }}" type: "Opaque" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#05.verified.yaml new file mode 100644 index 00000000000..a6d9456f19e --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#05.verified.yaml @@ -0,0 +1,43 @@ +--- +apiVersion: "apps/v1" +kind: "Deployment" +metadata: + name: "special-resource-with-hyphens-deployment" + labels: + app.kubernetes.io/name: "my-chart" + app.kubernetes.io/component: "Special-resource-with-hyphens" + app.kubernetes.io/instance: "{{.Release.Name}}" +spec: + template: + metadata: + labels: + app.kubernetes.io/name: "my-chart" + app.kubernetes.io/component: "Special-resource-with-hyphens" + app.kubernetes.io/instance: "{{.Release.Name}}" + spec: + containers: + - image: "my-image:latest" + name: "Special-resource-with-hyphens" + envFrom: + - configMapRef: + name: "special-resource-with-hyphens-config" + ports: + - name: "http" + protocol: "TCP" + containerPort: 80 + - name: "https" + protocol: "TCP" + containerPort: 443 + imagePullPolicy: "Always" + selector: + matchLabels: + app.kubernetes.io/name: "my-chart" + app.kubernetes.io/component: "Special-resource-with-hyphens" + app.kubernetes.io/instance: "{{.Release.Name}}" + replicas: 1 + revisionHistoryLimit: 5 + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: "RollingUpdate" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#06.verified.yaml new file mode 100644 index 00000000000..09368ba146c --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#06.verified.yaml @@ -0,0 +1,24 @@ +--- +apiVersion: "v1" +kind: "Service" +metadata: + name: "special-resource-with-hyphens-service" + labels: + app.kubernetes.io/name: "my-chart" + app.kubernetes.io/component: "Special-resource-with-hyphens" + app.kubernetes.io/instance: "{{.Release.Name}}" +spec: + type: "ClusterIP" + selector: + app.kubernetes.io/name: "my-chart" + app.kubernetes.io/component: "Special-resource-with-hyphens" + app.kubernetes.io/instance: "{{.Release.Name}}" + ports: + - name: "http" + protocol: "TCP" + port: 80 + targetPort: 80 + - name: "https" + protocol: "TCP" + port: 443 + targetPort: 443 diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#07.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#07.verified.yaml new file mode 100644 index 00000000000..46646f44863 --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#07.verified.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: "v1" +kind: "ConfigMap" +metadata: + name: "special-resource-with-hyphens-config" + labels: + app.kubernetes.io/name: "my-chart" + app.kubernetes.io/component: "Special-resource-with-hyphens" + app.kubernetes.io/instance: "{{.Release.Name}}" +data: + ORIGINAL_ENV: "{{ .Values.config.Special_resource_with_hyphens.ORIGINAL_ENV }}"